Base de connaissances Šikula Robotik

Compiler pour la BeagleBoneBlack

Cet article est le troisième d'une série à propos de la BeagleBoneBlack par l'association Šikula Robotik. Dans l'article précédent, nous avons mis en place une connexion réseau. Dans cet article nous allons voir comment compiler pour la BeagleBoneBlack de trois manières différentes : compilation sur cible, compilation croisée et compilation distribuée.

Compilation sur cible

La manière la plus simple de compiler un programme est certainement de lancer la compilation sur la machine sur laquelle le programme sera déployé. La BBB a suffisamment de puissance pour compiler de petits programmes. En fait, avec beaucoup de patience, elle peut même compiler de gros logiciels (plusieurs jours peuvent être requis).

Pour commencer, installons les suites logicielles “gcc” et “make” (“pacman -S gcc make”). Le premier est la “GNU Compiler Collection” dans sa version ARMv7. Son nom complet (dans la convention “architecture-vendeur-os-abi-gcc-version”) est armv7l-unknown-linux-gnueabihf-gcc-4.7.2. En fait, avec ses dépendances, il apporte plusieurs compilateurs (pour c, c99, c++, etc...), un éditeur de liens, quelques programmes pour manipuler les binaires, et une bibliothèque standard. Le second paquet est pour l'outil "GNU Make". Celui-ci aide à contrôler la génération d'exécutables.

Créons un projet simpliste avec seulement un fichier C++ nommé hello.cpp:

    #include <iostream>

    int main() {
        std::cout << “Hello world” << std::endl;
        return 0;
    }

Comme make dispose de règles implicites, nous pouvons simplement taper “make hello”. Make cherchera à construire hello, mais comme aucune règle n'est donnée explicitement dans le fichier Makefile, il va utiliser la règle implicite qui stipule qu'une “cible ayant un seul fichier .cpp portant le même nom que la cible” peut être contruit directement. Le programme hello est donc construit et peut être exécuté (“./hello”).

Comme vous pouvez voir, tout se passe exactement comme sur une station de travail traditionnelle. Cependant une différence majeure est que la compilation est assez lente, compte tenu da la faible puissance de calcul de la cible. C'est là qu'intervient la compilation croisée.

Compilation croisée

Puisque la cible a assez peu de puissance de calcul, it serait bien d'être capable de compiler avec un autre ordinateur qui dispose de beaucoup plus de ressources. Ce processus est appelé compilation croisée car nous compilons sur un ordinateur qui a une architecture différente de la cible.

Les outils de compilation sont un ensemble composé de compilateurs, d'utilitaires pour manipuler les binaires, et d'une libc. En ce qui concerne ArchLinuxARM, cette suite utilise actuellement gcc 4.7.2, binutils 2.23.1 and glibc 2.17, cette dernière étant elle-même compilée pour interagir avec un noyau 3.6.3 ou plus récent. Un fichier de configuration pour crosstool-ng est disponibe si vous souhaitez compiler le cross-compilateur vous-même.

Pour les utilisateur de Linux sous architecture x86_64, une archive pré-compilée est disponible et c'est celle-ci que nous utiliserons dans cet article. Téléchargez l'archive étiquetée "ARMv7l with hard float" sur le site web de ArchLinuxARM, et décompressez-la tout simplement (“tar xf x-tools7h.tar.xz”). Tout ce dont nous aurons besoin est contenu dans le dossier créé.

Essayons de compiler le même hello.cpp que précédemment, mais cette fois utilisons la compilation croisée en spécifiant le chemin du compilateur que nous souhaitons utiliser.

    CXX=/opt/x-tools7h/arm-unknown-linux-gnueabi/bin/armv7l-unknown-linux-gnueabihf-g++ make hello

Tout fonctionne parfaitement. Nous pouvons vérifier l'exécutable généré (“file hello”).

    hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.27, not stripped

C'est bien un exécutable pour Linux sur ARM ! En copiant ce binaire sur la cible (“scp hello user@192.168.2.1:hello”) et en l'exécutant, nous voyons que tout fonctionne à merveille.

Pour de plus gros projets, il faut remplacer tous les appels à g++, gcc, etc... par des appels équivalents du cross-compilateur. Vérifiez dans la documentation de vos outils s'il existe une manière simple de le faire. Lorsque vous écrivez vos propres Makefile, n'oubliez pas d'ajouter quelque chose comme ce qui suit pour prendre en charge à la fois à compilation locale et la compilation croisée. Évidemment, il faut utiliser ces variables dans vos règles.

    AR=$(CROSS_COMPILE)ar
    AS=$(CROSS_COMPILE)as
    CC=$(CROSS_COMPILE)cc
    CXX=$(CROSS_COMPILE)g++

Avec un tel Makefile, vous pouvez cross-compiler avec “CROSS_COMPILE=/opt/x-tools7h/arm-unknown-linux-gnueabi/bin/armv7l-unknown-linux-gnueabihf- make”.

Compilation distribuée

La compilation distribuée est un mélange de ces deux précédentes solutions. L'idée est de compiler avec l'aide d'autres ordinateurs. Dans le présent article, nous allons utiliser la BBB comme calculateur voulant de l'aide et mon portable 8-cœurs comme assistant. Vous pouvez imaginer des configurations plus complexes avec plusieurs ordinateurs assistants ou même une grille de calcul complètement constituée de cross-compilateurs si vous avez besoin.

Notez que disposer de 242 cœurs ne va pas réduire magiquement le temps de compilation à zéro. Il y a en fait un coût introduit par le mécanisme de distribution, certaines tâches sont non-compressibles et finalement certaines tâches doivent être exécutées séquentiellement.

La distribution est gérée par distcc, nous devons donc l'installer (“pacman -S distcc”). La même opération doit être faite sur les PC assistants (en réalité nous voulons distccd sur les assistants, qui est probablement fourni dans le même paquet... référez-vous à la documentation de votre distribution).

Puisque la méthode pour lancer le serveur distccd est probablement différente d'une distribution à l'autre, je présente ici la manière “manuelle en une seule ligne dans la console”. Encore une fois, RTFM reportez-vous à la documentation de votre distribution si vous voulez quelque chose de plus simple à utiliser.

    DISTCCD_PATH="/opt/x-tools7h/arm-unknown-linux-gnueabi/bin/" distccd -j 9 -a 192.168.2.1 --no-detach –daemon

Avec cette ligne de commande, j'ai choisi d'autoriser à lancer jusqu'à 9 tâches (en utilisant la règle expérimentale courante N cœurs + 1) et ai donné la liste des IPs autorisées (ici seulement la BBB). N'importe quelle IP accessible est acceptable, que ce soit sur le réseau que nous avons monté dans l'article précédent, comme ici, ou via n'importe quel réseau local à travers Ethernet ou WiFi par exemple. Évidemment, j'ai également précisé le chemin du compilateur.

Sur la BBB, nous pouvons indiquer la liste des assistants possibles dans le fichier de configuration de distcc ou la passer via la variable d'environnement DISTCC_HOSTS. Puisque les IPs sont susceptibles de changer assez souvent, la seconde version est presentée ici. Un mélange des deux peut être fait, en définissant des adresses par déraut dans le fichier de configuration et en changeant ces adresses ponctuellement selon le besoin par la variable d'environnement. S'il y a plusieurs assistants, il suffit de separer les IPs par des espaces. Notez que vous pouvez également utiliser localhost.

    DISTCC_HOSTS="192.168.2.10" CXX="distcc armv7l-unknown-linux-gnueabihf-g++" make -j3

Notez que le compilateur est désormais “distcc armv7l-unknown-linux-gnueabihf-g++” (avec la commande distcc en préfixe, n'oubliez pas l'espace), et que j'ai choisi de compiler 3 fichiers à la fois.

Comparaison du temps de compilation

Il est intéressant de comparer le temps de compilation des différentes méthodes. Pour cela, j'ai choisi un petit projet de 9 fichiers C++.

Ce projet a été compilé sur cible avec une et deux tâches. Il a également été cross-compilé avec une, deux et neuf tâches, et enfin compilé par compilation distribuée avec une, deux et neuf tâches.

Temps de compilation avec différentes méthodes. Un temps court est préférable.

Dénomination Type Tâches Temps Amélioration
Make Compilation sur cible 1 20.634s Référence
Make -j2 Compilation sur cible 2 22.394s (109%) ÷1.08
Distcc make Compilation distribuée 1 6.593s (32%) ×3.13
Distcc make -j2 Compilation distribuée 2 4.794s (23%) ×4.30
Distcc make -j9 Compilation distribuée 9 5.528s (27%) ×3.73
Cross make Compilation croisée 1 2.493s (12%) ×8.28
Cross make -j2 Compilation croisée 2 1.459s (7%) ×13.8
Cross make -j9 Compilation croisée 9 0.737s (4%) ×28

Sans surprise, la compilation croisée est le procédé le plus rapide. Sans surcharge et avec huit cœurs cadensés à 2.2Ghz, le projet est compilé 28 fois plus vite que sur la BBB. Notez que les fichiers étaient stockés dans la RAM du portable qui est bien plus rapide qu'un accès à la carte SD.

Lorsque le projet à compiler utilise des bibliothèques externes, la compilation croisée est un peu plus difficile. Il faut garder une copie du système de la cible à jour sur chaque station susceptible de cross-compiler afin que tous les fichiers nécessaires soient présents, etc. Dans ce cas, la compilation distribuée peut être un compromis acceptable.

Dans la solution distribuée, le projet a été compilé 4 fois plus vite. Avec trop de tâches, la BBB n'arrive pas à traiter assez vite toutes les réponses rapides et une petite perte de performance est visible.

Pour finir, lorsque aucun assistant n'est disponible, compiler sur la cible peut être une solution de secours. Après tout, lorque le projet est bien architecturé, il est rare d'avoir à recompiler tout lorsqu'une petite modification est faite.

Prochain article

C'est la fin de ce troisième article. La prochaine fois, nous parlerons du sujet connexe qu'est le déverminage (debug).