6 points par GN⁺ 2025-07-21 | 3 commentaires | Partager sur WhatsApp
  • L’équipe de développement de FFmpeg a annoncé un gain de performances pouvant atteindre 100x grâce à du code assembleur écrit à la main
  • Ce correctif n’accélère pas l’ensemble du programme, mais seulement certaines fonctions
  • Avec la prise en charge d’AVX512 sur les CPU récents, le gain atteint 100x, et 64 % sur les systèmes prenant en charge AVX2
  • Cette amélioration s’applique principalement à des filtres peu connus
  • L’optimisation automatique du compilateur montre encore un écart de performances face au code assembleur écrit à la main

Amélioration des performances de FFmpeg : ce que signifie réellement un gain de vitesse de 100x

Dernier correctif et principales améliorations

  • Les développeurs du projet FFmpeg soulignent avoir obtenu un « gain de vitesse de 100x » dans cet outil open source et multiplateforme de transcodage multimédia en appliquant du code assembleur écrit à la main
  • Ils précisent toutefois clairement que cette affirmation ne concerne pas l’ensemble de FFmpeg, mais une seule fonction
  • Cette optimisation exceptionnelle a été réalisée dans la fonction rangedetect8_avx512 : elle apporte jusqu’à 100x de gain sur les processeurs récents prenant en charge AVX512, et environ 64 % sur le chemin AVX2
  • Cette fonctionnalité s’applique à un filtre peu connu, qui n’avait jusqu’ici pas été prioritaire en développement, mais qui bénéficie désormais d’une optimisation du traitement parallèle via SIMD (single instruction, multiple data)

Explications des développeurs et contexte technique

  • Les développeurs de FFmpeg ont indiqué sur Twitter : « cette seule fonction est devenue 100x plus rapide, pas l’ensemble de FFmpeg »
  • Dans des explications complémentaires, ils ont ajouté que cette fonction pouvait, selon le système, offrir jusqu’à 100 % d’accélération
  • Cette amélioration des performances repose sur la technologie SIMD, qui augmente fortement l’efficacité du traitement parallèle sur les puces récentes

Pourquoi l’écriture manuelle en assembleur reste importante

Approches d’optimisation hier et aujourd’hui

  • Dans les années 1980 et 1990, le code assembleur écrit à la main était un outil clé pour accélérer les jeux et divers logiciels dans des environnements matériels contraints
  • Aujourd’hui, la plupart des compilateurs modernes convertissent le code de langages de haut niveau en assembleur, mais les limites d’optimisation du compilateur, notamment dans l’allocation des registres, font que l’assembleur écrit à la main peut encore offrir de meilleures performances
  • FFmpeg fait partie des rares projets qui conservent cette philosophie d’optimisation, et propose même son propre cours d’assembleur

L’influence de FFmpeg dans l’écosystème

  • Les bibliothèques et outils de FFmpeg fonctionnent sur Linux, Mac OS X, Microsoft Windows, BSD, Solaris et d’autres environnements
  • Des lecteurs vidéo populaires comme VLC utilisent aussi les bibliothèques libavcodec et libavformat de FFmpeg
  • Cela illustre l’importance technique de FFmpeg au sein du vaste écosystème open source de l’encodage/décodage multimédia

Conclusion

  • Cette amélioration des performances reste limitée à certaines fonctions plutôt qu’à l’ensemble des fonctionnalités critiques, mais elle montre qu’il est possible de repousser les limites des performances
  • La combinaison d’optimisations spécialisées pour le matériel récent et de l’esprit open source continue de faire de FFmpeg une référence technique dans le traitement multimédia

3 commentaires

 
bobcat 2025-07-24

C’est vrai hier comme aujourd’hui.
De façon similaire, en portant une bibliothèque de codecs sur ARM, j’ai commencé par dérouler un à un les kernels écrits en SSE, et une fois tout déroulé et avec seulement la version scalaire restante, quand j’ai lancé des benchmarks en conditions réelles, l’écart de performances était significatif.

 
aer0700 2025-07-23

Un ingénieur capable d’écrire du code encore mieux optimisé que GCC… c’est impressionnant.

 
GN⁺ 2025-07-21
Commentaires sur Hacker News
  • Pendant 10 ans à faire des optimisations SIMD pour HEVC et autres, comparer la version assembly à la version C générique était presque devenu une blague, parce qu’on obtenait des chiffres énormes comme 100x. En réalité, ce genre de résultat signifie surtout qu’au départ l’efficacité était extrêmement faible. En usage réel, on n’est pas dans un microbenchmark où le cache est rempli et où l’on rappelle la même fonction des millions de fois d’affilée. Dans la pratique, elle peut n’être appelée qu’une seule fois parmi beaucoup d’autres tâches. Pour réduire les effets de cache, il faudrait tester sur une très grande zone mémoire, mais je me demande si c’est réellement fait.

    • Je me demande aussi si un logiciel de transcodage vidéo comme FFmpeg effectue des « macrobenchmarks » séparés. J’imagine qu’ils mesurent sur une longue durée les performances, la qualité, l’usage CPU, etc., sur diverses vidéos et combinaisons de conversion, mais pour ça il faudrait sans doute un matériel dédié et cohérent.

    • Question un peu à côté, mais comme tu sembles très expérimenté en SIMD, j’aimerais savoir si tu as déjà utilisé ISPC et ce que tu en penses. Je trouve toujours un peu absurde qu’il faille encore écrire soi-même du code SIMD et que les compilateurs généraux restent faibles en auto-vectorisation, surtout par contraste avec les kernels GPU où l’auto-vectorisation existe depuis longtemps.

    • Je pense que ffmpeg lui-même n’est pas si différent d’un microbenchmark. Fondamentalement, c’est une structure du genre while (read(buf)) write(transform(buf)).

    • Malheureusement, au-delà des problèmes de cache, il arrivait aussi que les développeurs parlent d’un gain de vitesse de 100 %, alors qu’en réalité cela concernait souvent un filtre très spécifique. Cela dit, l’information est généralement présentée de façon assez transparente.

    • Sur l’interprétation « c’était inefficace au départ », je pense surtout que ce qui compte, c’est le chiffre obtenu et le résultat lui-même.

  • L’article prête à confusion : à certains endroits il parle de 100x, à d’autres d’un gain de performance de 100 %. Par exemple, il écrit que « les performances de rangedetect8_avx512 ont augmenté de 100,73 % », alors que la capture d’écran montre 100,73x. Or 100x, c’est +9900 %, alors que 100 % de gain signifie être 2 fois plus rapide. Je me demande donc lequel des deux est correct.

    • D’après la capture d’écran, c’est clairement 100x (ou 100,73x), ce qui correspond à une amélioration de 9973 %. Il semble que le symbole % ait été mal utilisé dans le corps de l’article.

    • C’est 100x au niveau de la fonction individuelle, et 100 % (2x) au niveau du filtre complet.

    • Côté ffmpeg, ils parlent bien de 100x. Le 100 % ressemble à une coquille de l’article.

    • Le nom de la fonction semble lié à « 8 », et si elle travaille sur des valeurs 8 bits, alors si l’implémentation précédente était scalaire, AVX512 peut traiter 128 éléments d’un coup, donc un gain de 100x me paraît plausible.

  • Je laisse ici comme référence le guide officiel de ffmpeg pour l’écriture en assembly : https://news.ycombinator.com/item?id=43140614

  • Dans l’article, je trouve peu clair dans quels cas rangedetect8_avx512 est réellement utilisé, et quel est le gain de performance en temps réel sur l’ensemble du pipeline de conversion. Je me demande si cela a vraiment une portée pratique visible.

    • À l’époque des signaux vidéo analogiques, on encodait des signaux de contrôle à l’intérieur de la bande utile. Même après l’arrivée du DVD, les signaux numériques étant encore sortis en analogique, les valeurs de couleur sous 16 (sur 0 à 255) servaient de signal « plus noir que le noir », et c’était aussi le cas pour BluRay et HDMI. Aujourd’hui, on tend à utiliser toute la plage 0–255. Mais il reste fréquent que la distinction des plages de signal soit mal gérée, ce qui donne des vidéos anormalement sombres. Cette fonction semble servir à déterminer si les valeurs de pixels sont dans la plage 16–255 ou 0–255. Si l’on sait avec certitude qu’on est en 16–255, on peut économiser de l’information lors de l’encodage. Mais ce n’est qu’une supposition. Je travaille moi-même dans la vidéo, et j’ai honte de toujours mal gérer les niveaux de noir.

    • Ce filtre n’est pas utilisé pendant la conversion elle-même ; il sert à déterminer des informations de métadonnées, comme la plage de couleurs ou si l’alpha est premultiplied. La fonction en question concerne spécifiquement la détection de la plage de couleurs.

  • Je voulais mentionner une expérience intéressante : la seule raison pour laquelle j’ai écrit de l’assembly, c’était justement à cause du SIMD. J’en ai reparlé récemment, et j’avais oublié qu’à l’époque il fallait vraiment en passer par là. En fait, à cause de problèmes d’aliasing, le compilateur n’optimisait pas comme je le voulais. Une fois que j’ai pu lui indiquer clairement que les données ne seraient pas accédées autrement, et en utilisant un mot-clé non standard, le compilateur s’est mis à exploiter automatiquement les instructions SIMD. J’ai finalement supprimé l’assembly que j’avais écrit moi-même.

  • Il y a quelque chose d’ironique dans le fait que cette optimisation ne s’applique qu’à x86/x86-64 (AVX2, AVX512). Pendant longtemps, tout le monde utilisait x86, donc les optimisations SIMD avaient de bonnes chances d’être largement applicables, mais les nouvelles architectures d’extension étaient vraiment mauvaises ou manquaient de compatibilité. Et maintenant que le SIMD x86 est enfin bon, x86 n’est plus vraiment le standard, donc il devient plus difficile de s’y appuyer.

    • AVX512 existe en plusieurs ensembles d’extensions, donc dès qu’on ne se limite pas aux instructions de base, la couverture varie selon les CPU. Les encodeurs récents ont aussi progressé en multithreading, mais il existe des limites. Sur un ancien projet embarqué, j’avais eu des problèmes de compatibilité avec les encodeurs vidéo du SoC, puis j’avais essayé ffmpeg et obtenu de meilleurs résultats en utilisant plusieurs cœurs CPU.
  • Franchement, je ne m’attendais pas à ce que de l’assembly soit plus rapide qu’un C optimisé. Les compilateurs modernes sont tellement bons que je pensais qu’écrire de l’assembly à la main n’apporterait qu’un gain minime. Ça m’a clairement montré que je me trompais. Je me suis dit qu’un jour il faudrait vraiment que j’apprenne sérieusement l’assembly.

    • En regardant les patchs concernés, l’implémentation de base (ff_detect_range_c) est un code C scalaire très générique, et le gain vient de la version AVX-512 de la même opération (ff_detect_rangeb_avx512). Les développeurs de FFmpeg préfèrent écrire directement l’assembly avec des macros indépendantes de la largeur vectorielle, mais on pourrait pratiquement exprimer la même chose avec les intrinsics Intel (au fond, la vraie différence serait surtout l’allocation des registres, donc pas grand-chose de substantiel). L’essentiel du gain, c’est la qualité de la vectorisation. Avec les compilateurs modernes, vectoriser des boucles qui ne sont pas triviales est presque impossible, et encore, ils n’essaient généralement qu’avec des options comme gcc -O3. Donc sur des opérations à grain fin comme ici en 8 bits, une vectorisation manuelle avec AVX/AVX2/AVX-512 peut facilement donner un gain d’au moins plusieurs dizaines de fois. Sur les CPU modernes, il arrive aussi qu’un code scalaire poussé à l’extrême soit plus rapide que ce que sort le compilateur, mais c’est très rare et cela demande énormément d’attention (chaînes de dépendances, pression sur les execution ports, etc.). J’ai moi-même déjà vu un gain de 40 %. Liens utiles : baseline C, version AVX512

    • Dès qu’on s’approche davantage de l’optimisation bas niveau, on finit en moins d’une heure par tomber sur un cas où le compilateur C fait soudain quelque chose d’étrange. Si c’est du code réellement appelé très souvent, les questions d’optimisation deviennent concrètement très importantes. Exemple : https://stackoverflow.com/questions/71343461/how-does-gcc-not-clang-make-this-optimization-deciding-that-a-store-to-one-str

    • Rien qu’en descendant au niveau des intrinsics SIMD, on peut déjà obtenir bien plus facilement de bonnes performances qu’avec le compilateur seul. J’avais même écrit un guide en plusieurs parties à ce sujet : https://scallywag.software/vim/blog/simd-perlin-noise-i

    • Dans presque toutes les bibliothèques C/C++, les portions vraiment critiques pour les performances utilisent de l’assembly écrit à la main (même pour des fonctions simples comme strlen). Les compilateurs produisent en général des résultats corrects, mais il y a peu de logiciels suffisamment importants pour justifier un tel investissement.

    • En réalité, le gain de performance ne vient pas de l’assembly mais d’AVX512. Ce kernel est si simple qu’en l’écrivant avec des intrinsics AVX512, on obtiendrait presque aucune différence entre le C et l’assembly. La raison du facteur 100x, c’est a) que min/max se fait en SIMD avec une seule instruction, alors qu’en scalaire il faut cmp + cmov, et b) qu’en précision 8 bits, chaque instruction AVX512 traite 64 éléments en parallèle. Au final, on peut même saturer la bande passante des caches L1 et L2 (sur Zen 5, 128B et 64B/cycle). En revanche, si l’on traite une frame entière, la taille mémoire peut forcer des accès au L3, auquel cas le gain est réduit, et s’il faut sortir du L3, il est encore plus faible.

  • Cela me rappelle Sound Open Firmware (SOF), qui peut aussi être compilé soit avec gcc, soit avec le compilateur commercial Cadence XCC (avec prise en charge des intrinsics SIMD Xtensa HiFi) : https://thesofproject.github.io/latest/introduction/index.html#toolchain-faq

  • C’est 100x, pas 100 %.