[RaspberryPi][dev] GCC, optimisations et benchmarks…

Mon Raspberry Pi, c’est mon petit passe-temps quand j’en ai marre de bosser. Mais tout de même, je me rassure en me disant que quand je fais du développement sur le Pi, ça me fait découvrir des trucs divers et variés.
Pour le coup, je suis encore sur ma détection / suivi de mouvement utilisant la Camera Board. L’algorithme général est pas bien compliqué :

  1. Acquérir l’image n-2 et la convertir
  2. Acquérir l’image n-1 et la convertir
  3. Acquérir l’image n et la convertir
  4. Comparer les images n-2 et n-1
  5. Comparer les images n-1 et n
  6. Déduire des deux étapes précédentes s’il y a eu un mouvement
  7. Centrer la caméra vers le mouvement
  8. Sauvegarder les images pertinentes sur le réseau
  9. Renommer n-1 en n-2 et n en n-1
  10. Boucler à l’étape 3

L’étape 7 est faite avec un script en python, les étapes 4, 5 et 6 faites avec un soft en C, le reste, c’est du bash script. Le script générant des fichiers régulièrement, je le fais tourner dans un ramdisk histoire de ne pas pourrir ma flash.
L’idée, c’est d’optimiser un peu le concept. Première piste que j’ai suivi, c’est de lancer en arrière-plan les différentes copies et renommage de fichier lorsque que ça pouvait être fait (genre l’étape 8, mais pas la neuf). En bash c’est pas bien compliqué, suffit de rajouter un « & » à la fin de la ligne. De même, le lancement du script python pour faire pivoter la caméra, je me fous pas mal d’attendre que le mouvement ce termine.

Puis, je me suis mis en tête de mesurer le temps d’exécution du soft en C, qui pour rappel prends 3 images en entrée, et me sort 3 images en sortie (diff entre n-2 et n-1, diff entre n-2 et le mouvement résultant). J’ai pris le timestamp au démarrage du soft et à la fin, une différence entre les 2, un printf, un boucle réalisant 100 fois l’opération histoire de faire un peu de stat’.

Ayant développé l’appli sur un PC, j’ai donc eu comme première mesure moyenne 99ms pour réaliser l’opération (sur un Core i7 3520M @ 2,9GHz), que j’ai ensuite refait sur le Pi à partir de la flash, et j’ai eu 597ms de moyenne (sur un Raspberry Pi 256Mo @ 700MHz). Alors oui… ça fait beaucoup. Puis test en ramdisk, et j’ai eu la surprise de voir que le temps moyen, 624ms est plus élevé qu’en flash ! Je n’ai pas encore compris pourquoi, il faudra que je vois s’il n’y pas une merde dans ma config de ramdisk.

En étant bête et con, j’ai ensuite overclocké le Pi avec l’outil de config fourni avec (raspi-config) à 1GHz. Résultat en flash : 477ms, et en ramdisk 478ms. Conclusion partielle, avec un overclock de 43%, je gagne 23% de perf. Bon, c’est toujours ça de pris.

Enfin, un peu plus malin, j’ai testé de compiler mon soft avec l’option d’optimisation de GCC (gcc -Ox source.c -o mon_exe), qui propose 5 niveaux de vitesse et une optimisation en taille. J’ai gardé l’overclock par flemme de redémarrer le R-Pi, et j’ai fait tourner l’appli en flash. Les résultats oscillent entre 354ms et 328ms, respectivement pour l’optimisation en taille et l’optimisation de niveau trois (-O3).

Avec un overclock et une option en plus dans GCC, je suis donc passé de 597 à 328ms 45% de temps en moins. L’air de rien, c’est pas si mal et ne m’a demandé aucun effort intellectuel d’optimisation de code.

Résumé des différents essais :

Au final, j’ai surtout tenté d’améliorer le temps d’exécution de la comparaison d’image. Mais il y a d’autres points qui pourraient être abordé :

  • Pouvoir lancer des commandes en fond dans le shell script en étant sur que leurs résultats seront prêts quand j’en aurai besoin
  • La comparaison d’image se fait en comparant les images pixels par pixels et couleur par couleurs (une boucle « for » avec 3 comparaisons dedans). Bien que le Rpi soit mono-coeur, peut être qu’on pourrait tenter de paralléliser ces comparaisons, ou bien de ne faire qu’une seule couleur
  • Vérifier que la partie « conversion d’image » est optimal. Pour le moment, je fais une capture en JPG, convertie ensuite et redimensionner en bmp (avec un rapport x16 entre les deux, permettant de créer la miniature bmp en sous échantillonnant les pixels de l’image capturé)

Et pour terminer, vérifier régulièrement que le truc marche encore après toutes ces bidouilles !

13 réflexions sur « [RaspberryPi][dev] GCC, optimisations et benchmarks… »

  1. Quelques remarques qui me viennent en tête pêle-mêle pour optimiser ton process:

    -Je ne comprend pas pourquoi tu compare 3 images pour determiner si il y a mouvement (ou plus globalement, un changement). L’image n et n-1 suffisent non ?

    -La plupart des algos de detection/tracking zappent purement et simplement la composante couleur. Appuie toi juste sur le canal vert (celui qui offre la plus grande fidélité et qui contribue le plus à l’info de luminosité) pour faire tes comparaisons, et voila tes calculs 3x plus rapides.

    -Il me semble qu’il existe des instructions SIMD sur le processeurs du Rasperri Pi que tu pourrais utiliser à ton avantage. A défaut d’être multi-coeur, tu pourrais quand même faire du calcul parallèle de cette manière.

    -Apparemment le Rasperri Pi supporte aussi OpenGL ES. Donc si il y a une sorte de GPU embarqué (je sais pas trop ce qu’il en est) autant en tirer partie, pour le traitement d’image c’est le top.

    -De manière générale j’ai l’impression que tu perd un temps fou à acquerir les images de la caméra en passant par du script et des fichiers… Tu dois bien avoir un moyen d’accéder directement au buffer du capteur depuis le code C ?

  2. @divide
    – Je compare 3 images pour connaitre la direction du mouvement. Si tu ne prends que les 2 premières images de mon exemple, tu arrives à détecter un changement, mais tu ne sais pas tu observes quelques choses qui va de gauche à droite ou de droite à gauche (enfin si, toi tu le sais parce que t’es un humain avec un cerveau, mais le programme lui ne voit qu’un amas de pixel qui ont changé, et qui comme tu l’aura deviné sont contenu dans le rectangle rouge.
    Après, je te l’accorde, si mon analyse était ultra rapide je me contenterai de déplacer la camera vers le centre du rectangle.
    Mais pour être honnête : 1) j’ai pas mal réfléchi à la cette question « 2 images versus 3 images », et bon… je ne suis pas complètement convaincu par ma solution 3 images.
    – ok pour la détection du le canal vert, ça me parait une bonne idée.
    – pour les instructions simd, j’y connais que pouic, je vais regarder, de même que pour le calcul via le GPU
    – je crois avoir vu trainer une API pour piloter la caméra en C++, ce qui me fait un peu peur étant donné que je ne suis pas hyper à mon aise dans ce domaine.

    @skaven
    J’ai pas tenté OpenCV, non. En fait ça me semblait vachement overkill et pas forcément en phase avec mon niveau d’exigence.
    En revanche quand tu parles de désactiver la gestion d’exception, tu parles de quoi exactement ? C’est dans le soft C ? Dans sa compilation ? Dans l’OS ?

    En tout cas merci pour les remarques constructives !

  3. Exception par compil.
    Tu peux aussi essayer de changer par compilation la précision des flottants (control87).
    Sinon, opencv n’est pas si overkill. l’API est bien faite et ca se compile presque tout seul.

  4. skaven a dit :
    Exception par compil.
    Tu peux aussi essayer de changer par compilation la précision des flottants (control87).
    Sinon, opencv n’est pas si overkill. l’API est bien faite et ca se compile presque tout seul.

    Mais genre en pratique je fais quoi pour les exceptions ? Pour les floats, je n’en utilise pas (enfin si, un seul, pour faire une division pas trop foireuse).

    J’ai identifié 2 API pour la camera, une ici : http://www.uco.es/investiga/grupos/ava/node/40 qui semble être interfacable avec opencv facilement, et installable facilement, et une autre par là : http://robotblogging.blogspot.fr/2013/10/an-efficient-and-simple-c-api-for.html

    J’vais creuser un peu.

    Ah oui sinon j’ai testé en ne regardant que ce qui change sur le vert et pas le reste, ça ne change pas grand chose, 20ms de mieux.

  5. Ah ok, je comprend mieux l’algo sous-jacent (mouvement de la boite englobante des différences n-2,n-1 et n-1,n).
    Généralement pour faire du tracking on cherche localement comment des blocs de pixels se déplacent au sein de l’image (c’est comme ça que marche la compression vidéo par exemple). Mais dans ton cas ca serait un peu overkill, ton approche à l’avantage d’être légère en terme d’algo et de calcul.

  6. Virer les exceptions avec GCC, ça revient à ajouter le flag -fno-exceptions au moment de la compilation (de la même façon que tu as ajouté -O3). Évidemment, si tu as du code qui utilise des blocs d’exception (try/catch), ce code ne compilera plus. C’est une petite optimisation qui peut faire la différence, sachant que de toute façon, l’usage des exceptions en C++ a toujours été controversé et nombreux sont ceux à recommander de s’en passer autant que possible. J’avoue ne pas savoir si ça change quelque chose avec du code purement en C.

  7. Disons que les exceptions et les ‘if return statment » sont plus complémentaires qu’autre chose, par contre il faut bien délimiter leurs usages respectifs. Mais pour un programme si petit c’est pas bien grave.

    Je rejoins skaven sur l’utilisation d’opencv, ça te facilitera grandement la vie et niveau perfs tu ne pourras je pense jamais faire mieux. (http://docs.opencv.org/doc/tutorials/core/table_of_content_core/table_of_content_core.html#table-of-content-core)

  8. Pour opencv, tu récupère le package. tu génères les makefiles avec cmake. tu lances make. c’est tout.
    J’aime pas les machins overkill/lourds/etc mais entre la facilité d’installation/compilation et les bénéfices de la lib concernant les intrinsèques/GPU, ya pas photo!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *