2 puntos por GN⁺ 2025-02-23 | Aún no hay comentarios. | Compartir por WhatsApp
  • La primera lección de ensamblador de FFmpeg presenta, a nivel introductorio, por qué el SIMD escrito a mano es necesario en el procesamiento multimedia y las convenciones de FFmpeg para escribir funciones
  • Los lectores objetivo deben conocer punteros en C y conceptos de escalares, vectores, suma y multiplicación de nivel preparatoria; la lección se desarrolla con base en x86 de 64 bits y sintaxis Intel
  • En FFmpeg, assembly function, SIMD y vectorise se usan casi con el mismo significado, y esta forma de procesar varios elementos de datos al mismo tiempo encaja muy bien con el procesamiento de imagen, video y audio
  • En rendimiento se prefiere el ensamblador manual: los intrinsics suelen ser entre 10 y 15% más lentos, y en dav1d se comparan casos donde la vectorización automática logra cerca de 2x mientras que una versión escrita a mano llega hasta 8x
  • La primera función de ejemplo usa x86inc.asm, INIT_XMM sse2, cglobal, movu, paddb y RET para sumar con SIMD 16 bytes de dos buffers uint8_t y volver a guardar el resultado en el primer buffer

Objetivo de la lección y conocimientos previos

  • FFmpeg Assembly Language Lesson One es una lección introductoria que cubre las bases de cómo se escribe el lenguaje ensamblador en FFmpeg y ayuda a entender qué ocurre realmente dentro de la computadora
  • Se requieren los siguientes conocimientos
    • Lenguaje C, en especial punteros
    • Si no conoces C, se recomienda estudiar The C Programming Language
    • Conceptos de escalares y vectores, suma y multiplicación a nivel de matemáticas de preparatoria

Lenguaje ensamblador y SIMD

  • El lenguaje ensamblador es un lenguaje de programación que permite escribir de forma legible para humanos código que corresponde directamente a las instrucciones que procesa el CPU
  • El código ensamblador legible para humanos se transforma mediante un ensamblador en datos binarios de código máquina (machine code) que el CPU puede entender
  • El código ensamblador de FFmpeg es en su mayoría de tipo SIMD (Single Instruction Multiple Data)
    • Una sola instrucción opera sobre varios elementos de datos al mismo tiempo
    • También se le llama programación vectorial
    • La programación escalar común procesa un solo elemento de datos a la vez
  • SIMD encaja muy bien con el procesamiento de imagen, video y audio, donde se trabaja con grandes cantidades de datos colocados secuencialmente en memoria
  • En FFmpeg, las siguientes expresiones se usan casi con el mismo significado
    • assembly function
    • SIMD
    • vectorise
    • Se refieren a escribir a mano funciones en ensamblador para procesar varios elementos de datos al mismo tiempo

Por qué FFmpeg escribe ensamblador directamente

  • El objetivo principal es acelerar el procesamiento multimedia
    • Es común obtener mejoras de más de 10x al escribir código ensamblador
    • Esto es importante para reducir interrupciones en la reproducción de video en tiempo real
    • También puede reducir el consumo de energía y extender la duración de la batería
  • Las funciones de codificación y decodificación de video se usan muchísimo tanto por usuarios finales como en centros de datos, así que incluso pequeñas mejoras se acumulan rápido
  • FFmpeg usa ensamblador escrito a mano en lugar de intrinsics
    • Los intrinsics son funciones parecidas a C que corresponden a instrucciones de ensamblador
    • Normalmente son entre 10 y 15% más lentos que el ensamblador escrito a mano
    • Esta cifra puede variar según el compilador, y quienes defienden los intrinsics podrían no estar de acuerdo
    • También hay opiniones de que son difíciles de leer por el uso de Hungarian Notation
  • inline assembly puede seguir presente en algunas partes de código antiguo de FFmpeg o en proyectos como el Linux Kernel
    • Es una forma de escribir ensamblador directamente dentro del código C en vez de usar archivos separados
    • En proyectos como FFmpeg, prevalece la opinión de que es difícil de leer, no tiene un soporte amplio por parte de los compiladores y es complicado de mantener
  • Se recomienda ignorar, con fines de aprendizaje, la postura de que la vectorización automática del compilador es suficiente
    • En pruebas recientes del proyecto dav1d, la vectorización automática mostró una mejora de alrededor de 2x
    • Una versión escrita a mano pudo llegar hasta 8x

Alcance de la sintaxis y materiales de referencia

  • La lección se enfoca en ensamblador x86 de 64 bits
    • También se conoce como amd64 y funciona en CPUs Intel
    • El ensamblador para otros CPUs como ARM o RISC-V podría añadirse en el futuro
  • La sintaxis de ensamblador x86 incluye AT&T e Intel
    • La sintaxis AT&T es más antigua y se considera más difícil de leer que la sintaxis Intel
    • La lección usa sintaxis Intel
  • Los libros generales o materiales en línea como Stack Overflow pueden no ser especialmente útiles como referencia para el ensamblador de FFmpeg
    • Porque usa ensamblador de sintaxis Intel escrito a mano
    • Mucho material en línea se enfoca en programación de sistemas operativos, programación de hardware o código no SIMD
    • El ensamblador de FFmpeg sigue un enfoque particular especializado en procesamiento de imágenes de alto rendimiento
  • Los diagramas que visualizan instrucciones SIMD y su funcionamiento en la parte final de The Art of 64-bit assembly pueden ser útiles
  • Se ofrece un servidor de Discord para preguntas

Conceptos básicos de registros

  • Un registro es una zona dentro del CPU donde se procesan datos
  • El CPU no opera directamente sobre la memoria: primero carga los datos en registros, luego los procesa y después los vuelve a escribir en memoria
  • En ensamblador, por lo general no se puede copiar datos directamente de una ubicación de memoria a otra; primero deben pasar por un registro

Registros de propósito general

  • Un GPR (General Purpose Register) es un registro de uso general que puede contener datos o direcciones de memoria
    • Aquí puede almacenar valores de hasta 64 bits
    • También puede contener punteros
    • Puede realizar operaciones como suma, multiplicación o desplazamiento
  • Muchos libros de ensamblador dedican mucho espacio a los detalles de los GPR y su contexto histórico
  • En el código ensamblador de FFmpeg, los GPR suelen usarse más bien como andamiaje (scaffolding), por lo que gran parte de esa complejidad no es necesaria o está abstraída

Registros vectoriales y tamaños de datos

  • Un registro vectorial contiene varios elementos de datos
  • Los principales registros vectoriales de x86 son los siguientes
    • mm: registros MMX, de 64 bits, históricos y hoy poco usados
    • xmm: registros XMM, de 128 bits, ampliamente disponibles
    • ymm: registros YMM, de 256 bits, con cierta complejidad adicional al usarlos
    • zmm: registros ZMM, de 512 bits, con disponibilidad más limitada
  • La mayor parte de los cálculos en compresión y descompresión de video se basa en enteros, así que la lección también se centra en enteros
  • Un registro xmm de 128 bits puede interpretarse así
    • 16 bytes, de 8 bits cada uno
    • 8 words, de 16 bits cada uno
    • 4 doublewords, de 32 bits cada uno
    • 2 quadwords, de 64 bits cada uno
  • Estas abreviaturas serán importantes más adelante
    • byte: dato de 8 bits
    • word: dato de 16 bits
    • doubleword: dato de 32 bits
    • quadword: dato de 64 bits
    • double quadword: dato de 128 bits

El papel de x86inc.asm

  • x86inc.asm es una capa ligera de abstracción usada por FFmpeg, x264 y dav1d
  • Ofrece varias funciones para ayudar a quienes programan en ensamblador a escribir código con mayor facilidad
  • Una de las funciones importantes al inicio es poner etiquetas como r0, r1 y r2 sobre los GPR
    • Así no hace falta memorizar los nombres reales de los registros
    • Como en FFmpeg los GPR suelen cumplir principalmente un papel de andamiaje, esto reduce la carga al escribir código

Ejemplo simple de asm escalar

mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
  • La primera línea guarda el valor inmediato (immediate value) 3 en el registro r0 como quadword
    • Un valor inmediato es un valor almacenado dentro del propio código ensamblador, no uno traído desde memoria
  • En sintaxis Intel, el operando fuente de la derecha se mueve al operando destino de la izquierda
    • Puede leerse como r0q = 3
    • El orden es parecido al comportamiento de memcpy
  • El sufijo q en r0q indica que el registro se usa como quadword
  • Después, las operaciones ocurren así
    • inc hace que el valor pase a 4
    • dec lo regresa a 3
    • imul lo multiplica por 5, así que al final r0q vale 15
  • Las instrucciones legibles para humanos como mov e inc se llaman mnemonics
    • El ensamblador las convierte a código máquina
    • MOV, INC en mayúsculas y mov, inc en minúsculas significan lo mismo
    • En FFmpeg, los mnemonics se escriben en minúsculas y las mayúsculas se reservan para macros

Primer ejemplo de función SIMD

%include "x86inc.asm"
SECTION .text
;static void add_values(uint8_t *src, const uint8_t * src2)
INIT_XMM sse2
cglobal add_values, 2, 2, 2, src, src2
movu m0, [srcq]
movu m1, [src2q]
paddb m0, m1
movu [srcq], m0
RET
  • Esta función suma con SIMD los datos de src y src2, y vuelve a guardar el resultado en la ubicación de src
  • %include "x86inc.asm" incluye helpers, nombres predefinidos y macros desarrollados por las comunidades de x264, FFmpeg y dav1d
  • SECTION .text indica la sección donde se coloca el código ejecutable
    • Los datos constantes pueden ir en la sección .data
  • ;static void add_values(uint8_t *src, const uint8_t * src2) es un comentario que muestra la forma de los argumentos de la función en C
    • En ensamblador, el punto y coma ; funciona como comentario, igual que // en C
  • INIT_XMM sse2 habilita el uso de registros XMM y del conjunto de instrucciones sse2
    • Porque paddb es una instrucción sse2
  • cglobal add_values, 2, 2, 2, src, src2 define la función en C add_values
    • La función tiene 2 argumentos
    • Se usarán 2 GPR en la función, incluyendo los argumentos
    • Se usarán 2 registros XMM
    • Los dos últimos elementos son las etiquetas de argumentos src y src2
    • El código antiguo puede usar directamente GPR como r0 y r1 sin etiquetas de argumento

load, packed add, store

movu m0, [srcq]
movu m1, [src2q]
  • movu es una forma abreviada de movdqu, que significa move double quad unaligned
  • La alineación (alignment) se verá en una lección posterior; aquí basta con entenderlo como una instrucción que mueve 128 bits desde [srcq]
  • En mov, los corchetes significan que se desreferencia una dirección
    • Es un concepto parecido a *src en C
    • Esta operación es un load
  • El sufijo q señala el tamaño del puntero
    • En un sistema de 64 bits, representa los 8 bytes del tamaño de un puntero en C
    • x86asm usa 32 bits en sistemas de 32 bits
    • El load real es de 128 bits
  • Los registros vectoriales se referencian con el nombre abstraído m0 en lugar del nombre completo como xmm0
    • Esto se relaciona con cómo, en lecciones posteriores, el mismo código puede adaptarse a varios tamaños de registro SIMD
paddb m0, m1
  • paddb suma entre sí los elementos byte de cada registro
  • El prefijo p significa packed y se usa para distinguir instrucciones vectoriales de las escalares
  • El sufijo b indica suma a nivel de byte
  • Al sumar dos registros con 16 bytes cada uno, se suman los elementos correspondientes por posición, como a+q, b+r, c+s
movu [srcq], m0
RET
  • movu [srcq], m0 vuelve a escribir los datos del resultado en la dirección apuntada por el puntero srcq
    • Esta operación es un store
  • RET es un macro que indica que la función retorna
  • Casi todas las funciones en ensamblador de FFmpeg modifican los datos recibidos como argumento en lugar de devolver valores
  • En la tarea, lo siguiente será crear punteros a función para las funciones de ensamblador disponibles y usarlos

Aún no hay comentarios.

Aún no hay comentarios.