2 Punkte von GN⁺ 2025-02-23 | Noch keine Kommentare. | Auf WhatsApp teilen
  • Die erste Assembly-Lektion von FFmpeg erklärt auf Einstiegsniveau, warum handgeschriebenes SIMD-Assembly in der Multimedia-Verarbeitung nötig ist und welche Konventionen FFmpeg für das Schreiben von Funktionen verwendet.
  • Zielgruppe sind Leser, die C-Zeiger sowie Konzepte wie Skalare, Vektoren, Addition und Multiplikation auf Oberstufenniveau kennen; die Lektion basiert auf x86 64-bit und Intel-Syntax.
  • In FFmpeg werden assembly function, SIMD und vectorise nahezu gleichbedeutend verwendet; die Verarbeitung mehrerer Datenelemente auf einmal passt gut zur Bild-, Video- und Audioverarbeitung.
  • Aus Performance-Gründen wird manuelles Assembly bevorzugt; intrinsics sind in der Regel 10–15 % langsamer, und es werden Fälle verglichen, in denen die automatische Vektorisierung von dav1d etwa 2x erreichte, die handgeschriebene Version dagegen bis zu 8x.
  • Die erste Beispielfunktion nutzt x86inc.asm, INIT_XMM sse2, cglobal, movu, paddb und RET, um die 16-Byte-Werte zweier uint8_t-Puffer per SIMD zu addieren und wieder im ersten Puffer zu speichern.

Ziel der Lektion und vorausgesetztes Wissen

  • FFmpeg Assembly Language Lesson One behandelt die Grundlagen, wie in FFmpeg Assembly-Sprache geschrieben wird, und hilft dabei zu verstehen, was im Inneren des Computers tatsächlich passiert.
  • Erforderliches Wissen:
    • Die Programmiersprache C, insbesondere Zeiger
    • Wer C nicht kennt, sollte The C Programming Language durcharbeiten.
    • Konzepte aus der Schulmathematik wie Skalare und Vektoren, Addition und Multiplikation

Assembly-Sprache und SIMD

  • Assembly-Sprache ist eine Programmiersprache, in der Code in für Menschen lesbarer Form geschrieben wird und direkt den Befehlen entspricht, die die CPU ausführt.
  • Der menschenlesbare Assembly-Code wird durch einen Assembler in binäre Maschinencode(machine code)-Daten umgewandelt, die die CPU versteht.
  • Der Assembly-Code von FFmpeg liegt größtenteils in Form von SIMD(Single Instruction Multiple Data) vor.
    • Ein einzelner Befehl wirkt gleichzeitig auf mehrere Datenelemente.
    • Das wird auch Vektorprogrammierung genannt.
    • Normale skalare Programmierung verarbeitet jeweils ein Datenelement auf einmal.
  • SIMD eignet sich gut für Bild-, Video- und Audioverarbeitung, bei der viele sequenziell im Speicher abgelegte Daten verarbeitet werden.
  • In FFmpeg werden die folgenden Ausdrücke nahezu gleichbedeutend verwendet:
    • assembly function
    • SIMD
    • vectorise
    • Gemeint ist, dass man eine Assembly-Funktion von Hand schreibt, um mehrere Datenelemente auf einmal zu verarbeiten.

Warum FFmpeg Assembly direkt schreibt

  • Das Kernziel ist die Beschleunigung der Multimedia-Verarbeitung.
    • Durch Assembly-Code sind Geschwindigkeitssteigerungen von über 10x üblich.
    • Das ist wichtig, um Ruckeln bei der Videowiedergabe in Echtzeit zu reduzieren.
    • Es kann den Energieverbrauch senken und die Akkulaufzeit verlängern.
  • Funktionen für Video-Encoding und -Decoding werden von Endnutzern und in Rechenzentren sehr häufig verwendet, daher summieren sich selbst kleine Verbesserungen schnell.
  • FFmpeg verwendet statt intrinsics handgeschriebenes Assembly.
    • intrinsics sind C-ähnliche Funktionen, die Assembly-Befehlen entsprechen.
    • Sie sind in der Regel 10–15 % langsamer als handgeschriebenes Assembly.
    • Diese Zahl hängt vom Compiler ab, und Befürworter von intrinsics könnten ihr widersprechen.
    • Es gibt auch die Ansicht, dass sie wegen der Verwendung von Hungarian Notation schwer lesbar sind.
  • inline assembly kann noch in einigen älteren FFmpeg-Codebestandteilen oder in Projekten wie dem Linux Kernel vorhanden sein.
    • Dabei wird Assembly nicht in einer separaten Datei, sondern direkt in C-Code geschrieben.
    • In Projekten wie FFmpeg überwiegt die Ansicht, dass dies schwer lesbar ist, nicht breit genug von Compilern unterstützt wird und schwer zu warten ist.
  • Die Ansicht, dass automatische Vektorisierung durch den Compiler ausreiche, soll zu Lernzwecken ignoriert werden.
    • In jüngsten Tests des dav1d-Projekts zeigte automatische Vektorisierung eine Beschleunigung von etwa 2x.
    • Die handgeschriebene Version konnte bis zu 8x erreichen.

Syntaxumfang und Referenzen

  • Die Lektion konzentriert sich auf x86-64-bit-Assembly.
    • Es wird auch amd64 genannt und läuft auch auf Intel-CPUs.
    • Assembly für andere CPUs wie ARM oder RISC-V könnte künftig ergänzt werden.
  • Für x86-Assembly gibt es AT&T- und Intel-Syntax.
    • Die AT&T-Syntax ist älter und gilt als schwerer lesbar als die Intel-Syntax.
    • Die Lektion verwendet Intel-Syntax.
  • Allgemeine Bücher oder Online-Ressourcen wie Stack Overflow sind als Referenz für FFmpeg-Assembly möglicherweise nicht besonders nützlich.
    • Der Grund ist, dass handgeschriebenes Assembly in Intel-Syntax verwendet wird.
    • Viele Online-Ressourcen konzentrieren sich auf Betriebssystemprogrammierung, Hardwareprogrammierung oder Nicht-SIMD-Code.
    • FFmpeg-Assembly ist ein spezieller Ansatz, der auf hochperformante Bildverarbeitung ausgerichtet ist.
  • Die Diagramme in der zweiten Hälfte von The Art of 64-bit assembly, die SIMD-Befehle und deren Verhalten visualisieren, können hilfreich sein.
  • Für Fragen wird ein Discord-Server bereitgestellt.

Grundkonzept der Register

  • Register sind Bereiche innerhalb der CPU, in denen Daten verarbeitet werden.
  • Die CPU führt Operationen nicht direkt auf dem Speicher aus; sie lädt Daten in Register, verarbeitet sie dort und schreibt sie anschließend wieder in den Speicher.
  • In Assembly kann man Daten im Allgemeinen nicht direkt von einer Speicherposition an eine andere kopieren, sondern muss sie zuerst über ein Register führen.

General-Purpose-Register

  • GPR(General Purpose Register) sind universelle Register, die Daten oder Speicheradressen enthalten können.
    • Hier können sie Werte bis zu 64 bit enthalten.
    • Sie können auch Zeiger enthalten.
    • Operationen wie Addition, Multiplikation und Shifts sind möglich.
  • Viele Assembly-Bücher behandeln Details und historische Hintergründe von GPR ausführlich.
  • Im Assembly-Code von FFmpeg dienen GPR hauptsächlich als Gerüst(scaffolding); der Großteil ihrer Komplexität wird nicht benötigt oder abstrahiert.

Vektorregister und Datengrößen

  • Vektorregister enthalten mehrere Datenelemente.
  • Die wichtigsten Vektorregister auf x86 sind:
    • mm: MMX-Register, 64 bit groß, historisch und heute nicht mehr häufig verwendet
    • xmm: XMM-Register, 128 bit groß, breit verfügbar
    • ymm: YMM-Register, 256 bit groß, mit einigen Komplexitäten bei der Verwendung
    • zmm: ZMM-Register, 512 bit groß, nur eingeschränkt einsetzbar
  • Da die meisten Berechnungen bei Videokompression und -dekompression ganzzahlbasiert sind, konzentriert sich auch die Lektion auf Integer.
  • Ein einzelnes 128-bit-xmm-Register kann wie folgt interpretiert werden:
    • 16 byte zu je 8 bit
    • 8 word zu je 16 bit
    • 4 doubleword zu je 32 bit
    • 2 quadword zu je 64 bit
  • Abkürzungen werden später wichtig:
    • byte: 8-bit-Daten
    • word: 16-bit-Daten
    • doubleword: 32-bit-Daten
    • quadword: 64-bit-Daten
    • double quadword: 128-bit-Daten

Die Rolle von x86inc.asm

  • x86inc.asm ist eine leichtgewichtige Abstraktionsschicht, die in FFmpeg, x264 und dav1d verwendet wird.
  • Sie stellt verschiedene Funktionen bereit, die Assembly-Programmierern das Schreiben von Code erleichtern.
  • Eine der am Anfang wichtigen Funktionen ist, GPR mit Labels wie r0, r1 oder r2 zu versehen.
    • Man muss sich die tatsächlichen Registernamen nicht merken.
    • Da GPR in FFmpeg vor allem als Gerüst dienen, reduziert das den Schreibaufwand.

Einfaches skalares asm-Beispiel

mov r0q, 3
inc r0q
dec r0q
imul r0q, 5
  • Die erste Zeile speichert den Immediate Value 3 als quadword im Register r0.
    • Ein Immediate Value ist kein aus dem Speicher geladener Wert, sondern ein im Assembly-Code selbst gespeicherter Wert.
  • In Intel-Syntax wird der Quelloperand rechts in den Zieloperanden links übertragen.
    • Man kann es wie r0q = 3 lesen.
    • Die Reihenfolge ähnelt dem Verhalten von memcpy.
  • Das Suffix q in r0q zeigt an, dass das Register als quadword verwendet wird.
  • Danach geschieht Folgendes:
    • Durch inc wird der Wert zu 4.
    • Durch dec wird der Wert wieder zu 3.
    • Durch imul wird mit 5 multipliziert, sodass r0q am Ende 15 ist.
  • Menschenlesbare Befehle wie mov oder inc heißen mnemonics.
    • Der Assembler wandelt sie in Maschinencode um.
    • Großgeschriebenes MOV, INC und kleingeschriebenes mov, inc sind identisch.
    • In FFmpeg werden mnemonics kleingeschrieben; Großbuchstaben bleiben Makros vorbehalten.

Erstes SIMD-Funktionsbeispiel

%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
  • Diese Funktion addiert die Daten aus src und src2 per SIMD und speichert das Ergebnis wieder an der Position von src.
  • %include "x86inc.asm" bindet Helfer, vordefinierte Namen und Makros ein, die von den Communities rund um x264, FFmpeg und dav1d entwickelt wurden.
  • SECTION .text bezeichnet den Abschnitt, in dem ausführbarer Code liegt.
    • Konstante Daten können im Abschnitt .data liegen.
  • ;static void add_values(uint8_t *src, const uint8_t * src2) ist ein Kommentar, der die C-Funktionsargumente zeigt.
    • In Assembly dient das Semikolon ; als Kommentar, ähnlich wie // in C.
  • INIT_XMM sse2 aktiviert die Verwendung von XMM-Registern und des Befehlssatzes sse2.
    • Der Grund ist, dass paddb ein sse2-Befehl ist.
  • cglobal add_values, 2, 2, 2, src, src2 definiert die C-Funktion add_values.
    • Die Funktion hat 2 Argumente.
    • Die Zahl der in der Funktion verwendeten GPR, einschließlich der Argumente, ist 2.
    • Die Zahl der verwendeten XMM-Register ist 2.
    • Die letzten beiden Einträge sind die Argument-Labels src und src2.
    • Älterer Code kann ohne Argument-Labels direkt GPR wie r0 oder r1 verwenden.

Load, Packed Add, Store

movu m0, [srcq]
movu m1, [src2q]
  • movu ist eine Kurzform von movdqu und steht für move double quad unaligned.
  • Alignment wird in späteren Lektionen behandelt; hier kann man es als Befehl verstehen, der 128 bit aus [srcq] bewegt.
  • Eckige Klammern bei mov bedeuten, dass eine Adresse dereferenziert wird.
    • Das ist ähnlich wie *src in C.
    • Dieser Vorgang ist ein load.
  • Das Suffix q bezeichnet die Zeigergröße.
    • Auf 64-bit-Systemen steht es für 8 Byte, also die Größe eines C-Zeigers.
    • x86asm verwendet auf 32-bit-Systemen 32 bit.
    • Der tatsächliche load umfasst 128 bit.
  • Vektorregister werden nicht mit dem vollständigen Namen wie xmm0, sondern über das abstrahierte m0 referenziert.
    • Das hängt mit der später behandelten Methode zusammen, denselben Code für mehrere SIMD-Registergrößen nutzbar zu machen.
paddb m0, m1
  • paddb addiert die byte-Elemente der beiden Register miteinander.
  • Das Präfix p steht für packed und dient dazu, Vektorbefehle von skalaren Befehlen zu unterscheiden.
  • Das Suffix b steht für Addition auf byte-Ebene.
  • Wenn zwei Register mit 16 byte addiert werden, werden die entsprechenden Elemente positionsweise addiert, etwa a+q, b+r, c+s.
movu [srcq], m0
RET
  • movu [srcq], m0 schreibt die Ergebnisdaten zurück an die Adresse, auf die der Zeiger srcq zeigt.
    • Dieser Vorgang ist ein store.
  • RET ist ein Makro, das die Rückkehr aus der Funktion angibt.
  • Fast alle Assembly-Funktionen in FFmpeg ändern Daten, die als Argumente übergeben wurden, statt einen Wert zurückzugeben.
  • In der Aufgabe geht es anschließend darum, Funktionszeiger für verfügbare Assembly-Funktionen zu erstellen und zu verwenden.

Noch keine Kommentare.

Noch keine Kommentare.