6 puntos por GN⁺ 2025-07-21 | 3 comentarios | Compartir por WhatsApp
  • El equipo de FFmpeg anunció una mejora de rendimiento de hasta 100 veces mediante código ensamblador escrito a mano
  • Este parche mejora la velocidad solo en una función específica, no en todo el programa
  • Se comprobó una mejora de hasta 100 veces con soporte AVX512 en CPU modernas y de 64% con soporte AVX2
  • La mejora en cuestión se aplica principalmente a un filtro poco conocido
  • La optimización automática del compilador sigue mostrando una brecha de rendimiento frente al código ensamblador escrito a mano

La mejora de rendimiento de FFmpeg: qué significa realmente una aceleración de 100 veces

Parche más reciente y mejoras principales

  • El equipo del proyecto FFmpeg destacó haber logrado una aceleración de 100 veces en esta herramienta de transcodificación multimedia multiplataforma y de código abierto tras aplicar código ensamblador escrito manualmente
  • Sin embargo, el propio equipo aclaró que esta afirmación se refiere solo a una función individual, no a FFmpeg completo
  • Esta optimización sobresaliente se realizó en la función rangedetect8_avx512, con una mejora de hasta 100 veces en procesadores modernos compatibles con AVX512 y de alrededor de 64% en la ruta AVX2
  • Esta función se aplica a un filtro poco conocido que antes no estaba entre las prioridades de desarrollo, pero que ahora recibió una optimización de procesamiento paralelo mediante SIMD (instrucción única, múltiples datos)

Explicación del equipo y contexto técnico

  • El equipo de FFmpeg dejó claro en Twitter que “esta única función se volvió 100 veces más rápida; no todo FFmpeg”
  • En una explicación adicional, indicó que según el sistema podría haber mejoras de velocidad de hasta 100%
  • Esta mejora de rendimiento es resultado de la tecnología SIMD, que incrementa de forma importante la eficiencia del procesamiento paralelo en chips modernos

La importancia de escribir ensamblador a mano

Enfoques de optimización antes y ahora

  • En las décadas de 1980 y 1990, el código ensamblador escrito a mano era una herramienta clave para acelerar juegos y todo tipo de software en entornos de hardware limitados
  • Hoy, aunque la mayoría de los compiladores modernos convierten código de alto nivel en ensamblador, las limitaciones de optimización del compilador, como la asignación de registros, hacen que el ensamblador escrito a mano siga ofreciendo mejor rendimiento
  • FFmpeg es uno de los pocos proyectos que mantiene esta filosofía de optimización e incluso ofrece sus propios tutoriales de ensamblador

La influencia de FFmpeg en el ecosistema

  • Las bibliotecas y herramientas de FFmpeg funcionan en Linux, Mac OS X, Microsoft Windows, BSD, Solaris y otros entornos
  • Programas populares de reproducción de video como VLC también utilizan las bibliotecas libavcodec y libavformat de FFmpeg
  • Esto refleja la gran importancia técnica de FFmpeg dentro del amplio ecosistema de código abierto de codificación y decodificación multimedia

Cierre

  • Aunque esta mejora de rendimiento se limita a algunas funciones y no a toda la funcionalidad central, muestra un ejemplo de cómo se pueden superar los límites del desempeño
  • La combinación de optimizaciones especializadas para hardware moderno y el espíritu del código abierto sigue haciendo de FFmpeg un referente técnico en el procesamiento multimedia

3 comentarios

 
bobcat 2025-07-24

Esto era válido antes y sigue siéndolo ahora.
De forma similar, al portar una biblioteca de códecs a ARM, empecé por desarmar uno por uno los kernels hechos con SSE, y cuando terminé de desarmarlos todos y quedó solo la versión escalar, al correr benchmarks del mundo real la diferencia de rendimiento fue significativa.

 
aer0700 2025-07-23

Un ingeniero capaz de escribir código más optimizado que GCC... realmente impresionante.

 
GN⁺ 2025-07-21
Comentarios en Hacker News
  • Durante 10 años haciendo optimizaciones SIMD para HEVC y similares, comparar la versión en assembly con la versión normal en C casi era un chiste, porque salían números enormes como 100x. En realidad, resultados así implican que la eficiencia inicial era extremadamente baja. En uso real no estás en un entorno tipo microbenchmark, con la caché llena y llamando a la misma función millones de veces de forma repetida. Más bien, en una situación real podría llamarse solo una vez entre muchas otras tareas. Para reducir el efecto de la caché habría que crear una región de memoria de prueba muy grande, aunque no sé si realmente lo hacen.

    • Me da curiosidad si software de transcodificación de video como FFmpeg también hace "macrobenchmarks" por separado. Suena a que medirían rendimiento, calidad y uso de CPU durante largos periodos con distintas combinaciones de videos y conversiones, pero para eso haría falta hardware dedicado y consistente.

    • Es una pregunta un poco aparte, pero como parece que tienes mucha experiencia con SIMD, quería preguntarte si has usado ISPC y qué opinas. Todavía se siente algo absurdo que aún haya que escribir código SIMD a mano y que los compiladores generales sigan siendo débiles para la autovectorización, en contraste con los kernels de GPU, donde eso prácticamente siempre ha existido.

    • Creo que ffmpeg en sí tampoco es tan distinto de un microbenchmark. En esencia tiene una estructura como while (read(buf)) write(transform(buf)).

    • Por desgracia, además del tema de la caché, a veces los desarrolladores hablan de mejoras de 100% de velocidad cuando en realidad solo aplican a filtros muy específicos. Aun así, suelen comunicar la información de forma bastante transparente.

    • Sobre la interpretación de que "al principio era ineficiente", yo creo que lo importante es qué números salen; el resultado en sí es lo que importa.

  • El artículo confunde porque en unas partes habla de 100x y en otras de una mejora de 100%. Por ejemplo, dice que "el rendimiento de ‘rangedetect8_avx512’ aumentó 100.73%", pero la captura muestra 100.73x. Si fuera 100x, sería un aumento de 9900%; si fuera 100% de velocidad, sería el doble de rápido. Me pregunto cuál de las dos es la correcta.

    • Como se ve claramente en la captura, sí son 100x (o 100.73x), lo que implica una mejora de 9973% en velocidad. Parece que en el texto del artículo usaron mal el símbolo %.

    • A nivel de función individual son 100x; a nivel del filtro completo son 100% (2x).

    • Del lado de ffmpeg, la afirmación es 100x. Lo de 100% parece un error tipográfico del artículo.

    • El nombre de la función parece estar relacionado con '8', y si opera sobre valores de 8 bits, entonces si la implementación anterior era escalar, con AVX512 podría procesar 128 elementos de una vez, así que una mejora de 100x sí me parece posible.

  • Dejo también como referencia la guía oficial de ffmpeg para escribir assembly: https://news.ycombinator.com/item?id=43140614

  • En el artículo no queda claro en qué situaciones se usa realmente rangedetect8_avx512 ni cuánto mejora el rendimiento en tiempo real dentro del proceso completo de conversión. Me pregunto si de verdad tiene una relevancia práctica visible.

    • Antes, en la época de las señales de video analógicas, las señales de control se codificaban dentro de la banda. Incluso después del DVD, como las señales digitales seguían saliendo en analógico, se usaban valores de color por debajo de 16 (de 0 a 255) como señales "más negras que el negro", y con BluRay y HDMI también pasaba. Últimamente la tendencia ha cambiado hacia usar todo el rango 0~255. Pero todavía pasa mucho que no se distingue bien el rango de señal y el video sale arruinado y oscuro. Esta función parece servir para determinar si los valores de píxel están en 16~255 o en 0~255. Si se sabe con certeza que es 16~255, se puede ahorrar información al codificar. Pero esto es una suposición. Trabajo con video por mi profesión, y me da vergüenza admitir que siempre manejo mal el tratamiento del nivel de negro.

    • Ese filtro no se usa en el proceso de conversión en sí, sino para detectar información de metadatos, como el rango de color o si el alpha es premultiplied. La función en cuestión corresponde específicamente a detectar el rango de color.

  • Quiero mencionar una experiencia interesante. La única vez que escribí código en assembly también fue por SIMD. Hace poco hablé de eso y se me había olvidado por qué en ese entonces tuve que usar assembly. En realidad, el compilador no optimizaba como yo quería por un problema de aliasing. En cuanto dejé claro que no habría accesos a los datos de otra forma y usé una palabra clave no estándar, el compilador empezó a aprovechar automáticamente instrucciones SIMD. Al final quité el assembly que había escrito a mano.

  • Es un poco irónico que esta optimización solo aplique a x86/x86-64 (AVX2, AVX512). Durante bastante tiempo todo el mundo usaba x86, así que las optimizaciones SIMD tenían posibilidades de aplicarse ampliamente, pero las nuevas arquitecturas con extensiones realmente eran bastante malas o no tenían suficiente compatibilidad. Y ahora que por fin llegó un SIMD de x86 mejorado, x86 ya no es el estándar de facto, así que se volvió más difícil depender de él.

    • AVX512 tiene varios conjuntos de extensiones, así que si no usas solo las instrucciones básicas, el soporte puede variar bastante entre CPUs. Los codificadores modernos han mejorado mucho en rendimiento multihilo, pero también tienen límites. En un proyecto embebido hace tiempo sufrí con problemas de compatibilidad del codificador de video del SoC, probé ffmpeg y terminé obteniendo mejores resultados usando varios núcleos de CPU.
  • La verdad me sorprendió que assembly pudiera ser más rápido que C optimizado. Los compiladores modernos son tan buenos que pensé que escribir assembly a mano apenas daría una mejora mínima, pero claramente estaba equivocado. Me hizo decidir que algún día sí tengo que aprender assembly en serio.

    • Revisando los parches relacionados, la base existente (ff_detect_range_c) es código C escalar muy genérico, y la mejora de velocidad viene de la versión AVX-512 de la misma operación (ff_detect_rangeb_avx512). Los desarrolladores de FFmpeg prefieren escribir assembly directamente usando macros independientes del ancho vectorial, pero con intrinsics de Intel también parece posible expresar casi lo mismo (al final la diferencia práctica real sería sobre todo la asignación de registros). La esencia de la diferencia de rendimiento está en qué tan bien se vectoriza. Con compiladores modernos, vectorizar bucles que no sean triviales es casi imposible, y aun así solo lo intentan si les das opciones como gcc -O3. Por eso, en operaciones con unidades pequeñas como 8 bits, si vectorizas manualmente con AVX/AVX2/AVX-512, la diferencia de rendimiento fácilmente llega a varias decenas de veces. En CPUs modernas, a veces incluso se puede escribir código escalar extremadamente optimizado que corra más rápido que lo que produce el compilador, pero eso es raro y requiere mucho cuidado (cadenas de dependencias, carga sobre los execution ports, etc.). Yo mismo he visto mejoras de 40%. Enlaces relacionados: baseline C, versión AVX512

    • Si te acercas más a la optimización de bajo nivel, en menos de una hora te topas con casos donde el compilador de C empieza a hacer cosas extrañas. Si es código que realmente se llama con frecuencia extrema, los temas de optimización sí se vuelven muy importantes en la práctica. Ejemplo: https://stackoverflow.com/questions/71343461/how-does-gcc-not-clang-make-this-optimization-deciding-that-a-store-to-one-str

    • Incluso con solo bajar a intrinsics SIMD ya puedes sacar mucho más rendimiento con bastante más facilidad que dejándoselo al compilador. Alguna vez escribí una guía sobre eso en varias partes: https://scallywag.software/vim/blog/simd-perlin-noise-i

    • En casi todas las bibliotecas C/C++, los tramos críticos para el rendimiento usan assembly hecho a mano (incluso funciones simples como strlen). Los compiladores suelen producir resultados aceptables, pero muy pocos programas son lo bastante importantes como para que valga la pena invertir a ese nivel.

    • La mejora real no se debe al assembly, sino a AVX512. Este kernel es tan simple que, si lo escribes con intrinsics AVX512, casi no hay diferencia entre el código C y el assembly. La razón del salto de 100x es: a) min/max en SIMD se resuelven con una sola instrucción, mientras que en escalar se dividen en cmp + cmov; b) como la precisión es de 8 bits, cada instrucción AVX512 procesa 64 valores al mismo tiempo. Como resultado, hasta puede saturar el ancho de banda de caché L1 y L2 (en Zen 5, 128B y 64B/cycle). Eso sí, si procesas un frame completo, al tener que acceder a L3 por el tamaño de la memoria, esa ganancia se reduce a la mitad; y si ni siquiera cabe en L3, la ganancia baja aún más.

  • Me recordó a Sound Open Firmware (SOF), que también puede compilarse eligiendo entre gcc y el compilador comercial Cadence XCC (con soporte para intrinsics SIMD de Xtensa HiFi): https://thesofproject.github.io/latest/introduction/index.html#toolchain-faq

  • Es 100x, no 100%.