- 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.