- recall ist ein Dienst, der Meeting-Bots für Hunderte von Unternehmen anbietet und auf AWS eine große Infrastruktur betreibt
- Für einen kosteneffizienten Service wollte das Unternehmen die Hardwareleistung maximal ausnutzen
- Da die GPU-Verfügbarkeit bei Cloud-Anbietern in den letzten Jahren instabil war, wurde die Videobearbeitung auf der CPU statt auf der GPU durchgeführt
- Beim Profiling der Bots mit headlosem Chromium zeigte sich, dass der Großteil der CPU-Zeit nicht für die Videobearbeitung (Encoding/Decoding), sondern für die Speicher-Kopierfunktionen
__memmove_avx_unaligned_erms und __memcpy_avx_unaligned_erms verbraucht wurde
memmove und memcpy sind Funktionen zum Kopieren von Speicherblöcken in der C-Standardbibliothek (glibc)
memmove behandelt einige Sonderfälle beim Kopieren überlappender Speicherbereiche, aber beide Funktionen lassen sich als „Speicher-Kopier“-Funktionen einordnen
- Das Suffix
avx_unaligned_erms bedeutet, dass sie für Systeme mit Unterstützung für Advanced Vector Extensions (AVX) optimiert sind und auch nicht ausgerichtete Speicherzugriffe effizient behandeln
erms steht für Enhanced REP MOVSB/STOSB, eine Optimierung für schnelle Speicherbewegungen auf modernen Intel-Prozessoren. Man kann es als „schnellere Implementierung für bestimmte Prozessoren“ verstehen
- Das Profiling zeigte, dass diese Funktionen am häufigsten vom Python-WebSocket-Client aufgerufen wurden, der Daten empfängt
- Danach folgte Chromiums WebSocket-Implementierung, die Daten sendet
Das Problem mit WebSockets
- Es wurde ein lokaler WebSocket-Server verwendet, um rohe Videodaten aus der JS-Umgebung von Chromium an den Encoder zu übertragen
- Ein roher 1080p-30fps-Videostream erfordert eine hohe Bandbreite von mehr als 93 MB pro Sekunde
- Der Einsatz von WebSockets verursachte hohe Rechenkosten, wobei die Hauptursachen Fragmentierung und Maskierung waren
- Fragmentierung: Chromiums WebSocket-Implementierung fragmentiert Nachrichten über 131 KB in mehrere Frames. Rohe Videoframes von mehr als 3 MB wurden aufgeteilt und in mehr als 24 separaten Frames übertragen
- Maskierung: Aus Sicherheitsgründen maskiert WebSocket alle vom Client zum Server gesendeten Frames. Bei großen Datenmengen von über 100 MB pro Sekunde wird das zu einem spürbaren Overhead
Suche nach Alternativen
- Mit Browser-APIs ließ sich nur schwer etwas realisieren, das deutlich leistungsfähiger als WebSocket ist, daher wurde beschlossen, Chromium zu forken und benutzerdefinierte Funktionen zu implementieren
- Drei Alternativen wurden geprüft: raw TCP/IP, Unix Domain Socket und Shared Memory
- TCP/IP: Die Probleme von WebSocket mit Fragmentierung/Maskierung lassen sich vermeiden, aber die maximale Paketgröße ist klein, sodass Fragmentierung weiterhin ein Problem bleibt. Zudem entsteht Kopier-Overhead in den Kernel-Space
- Unix Domain Socket: Der Netzwerk-Stack lässt sich vollständig umgehen, aber es ist weiterhin eine Datenkopie zwischen User-Space und Kernel-Space erforderlich
- Shared Memory: Speicher, auf den mehrere Prozesse gleichzeitig zugreifen können. Chromium kann ohne Zwischenkopie direkt in den Shared Memory schreiben, und der Encoder kann ihn sofort lesen
Implementierung einer Shared-Memory-basierten Übertragung
- Um Daten im Shared Memory fortlaufend zu lesen und zu schreiben, wurde eine Ringpuffer-Struktur implementiert
- Anforderungen: lock-free, mehrere Produzenten/ein Konsument, variable Frame-Größen, Zero-Copy-Lesen, Sandbox-Freundlichkeit, Signalisierung mit geringer Latenz
- Vorhandene Ringpuffer-Implementierungen wurden evaluiert, aber keine erfüllte alle Anforderungen, daher wurde eine eigene Implementierung entwickelt
- Um Zero-Copy-Lesen zu unterstützen, wurden die Pointer in drei Zustände aufgeteilt: write, peek und read
- Für Thread-Sicherheit wurden atomare Operationen verwendet, und um neue Daten bzw. frei gewordenen Speicherplatz zu signalisieren, kamen named semaphores zum Einsatz
- Durch die Shared-Memory-basierte Ringpuffer-Implementierung und weitere Optimierungen konnte die CPU-Auslastung der Bots um bis zu 50 % gesenkt werden. Dadurch wurden letztlich jährlich mehr als eine Million US-Dollar an AWS-Kosten eingespart.
3 Kommentare
Hacker-News-Kommentare
Das ist die typische Geschichte eines Startups, das einen „gut genug“-Shortcut wählt und erst später optimiert.
Es gibt die Meinung, dass die hohe Bandbreite der Rohvideodaten überraschend ist.
Es gibt die Meinung, dass es kein AWS-Problem ist, sondern ein Problem verschwendeter CPU-Zyklen.
Es wird darauf hingewiesen, dass MTU und MSS in TCP/IP-Netzwerken im Vergleich zur Größe von Videoframes klein sind.
Es gibt die Meinung, dass man mit Chromiums Mojo keinen plattformspezifischen Code berücksichtigen muss.
Es gibt die Meinung, dass nicht das Netzwerk das Problem sei, sondern mangelndes Verständnis für Video-Codecs.
Die Transparenz wird gelobt, und es wird gesagt, man wünsche sich auch Transparenz bei den Produktpreisen.
Es wird erklärt, dass das Masking im WebSocket-Protokoll ein Versuch ist, Probleme mit Man-in-the-Middle-Angreifern zu lösen.
Es wird darauf hingewiesen, dass die Übertragung von Videodaten ohne Komprimierung seltsam ist.
Es wird gesagt, dass der anfängliche Ansatz, Rohvideo über WebSocket zu übertragen, überraschend ist.
Sie haben also von Anfang an falsch entwickelt..
„Dass der anfängliche Ansatz, Rohvideo über WebSocket zu übertragen, erstaunlich sei.“ Dem stimme ich zu.