Hot Path im JavaScript-Code der App durch WebAssembly ersetzen

Es geht immer schnell.

In meinen vorherigen Artikeln habe ich beschrieben, wie Sie mit WebAssembly das Bibliothekssystem von C/C++ ins Web bringen können. Eine der Anwendungen, in denen C/C++-Bibliotheken in großem Umfang genutzt werden, ist Squoosh. Mit dieser Webanwendung können Sie Bilder mit einer Vielzahl von Codecs komprimieren, die aus C++ in WebAssembly kompiliert wurden.

WebAssembly ist eine einfache virtuelle Maschine, die den in .wasm-Dateien gespeicherten Bytecode ausführt. Dieser Bytecode ist stark typisiert und so strukturiert, dass er viel schneller als JavaScript für das Hostsystem kompiliert und optimiert werden kann. WebAssembly bietet eine Umgebung zum Ausführen von Code, die von Anfang an für Sandboxing und Einbetten entwickelt wurde.

Meiner Erfahrung nach werden die meisten Leistungsprobleme im Web durch erzwungenes Layout und übermäßiges Painting verursacht. Aber hin und wieder muss eine App eine rechenintensive Aufgabe ausführen, die viel Zeit in Anspruch nimmt. Hier kann WebAssembly helfen.

The Hot Path

In Squoosh haben wir eine JavaScript-Funktion geschrieben, die einen Bildpuffer um ein Vielfaches von 90 Grad dreht. OffscreenCanvas wäre dafür zwar ideal, wird aber in den von uns angestrebten Browsern nicht unterstützt. Außerdem ist es in Chrome etwas fehlerhaft.

Diese Funktion durchläuft jedes Pixel eines Eingabebilds und kopiert es an eine andere Position im Ausgabebild, um eine Drehung zu erzielen. Für ein Bild mit 4.094 x 4.096 Pixeln (16 Megapixel) wären mehr als 16 Millionen Iterationen des inneren Codeblocks erforderlich, was wir als „Hot Path“ bezeichnen. Trotz dieser relativ großen Anzahl von Iterationen erledigen zwei der drei von uns getesteten Browser die Aufgabe in weniger als zwei Sekunden. Eine akzeptable Dauer für diese Art von Interaktion.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

Ein Browser benötigt jedoch über 8 Sekunden. Die Art und Weise, wie Browser JavaScript optimieren, ist sehr kompliziert und verschiedene Engines optimieren für unterschiedliche Dinge. Einige optimieren für die reine Ausführung, andere für die Interaktion mit dem DOM. In diesem Fall haben wir in einem Browser einen nicht optimierten Pfad gefunden.

WebAssembly hingegen ist vollständig auf die reine Ausführungsgeschwindigkeit ausgelegt. Wenn wir also eine schnelle, vorhersehbare Leistung für Code wie diesen in allen Browsern wünschen, kann WebAssembly helfen.

WebAssembly für vorhersehbare Leistung

Im Allgemeinen können JavaScript und WebAssembly dieselbe Spitzenleistung erzielen. Bei JavaScript ist diese Leistung jedoch nur über den „schnellen Pfad“ erreichbar und es ist oft schwierig, auf diesem „schnellen Pfad“ zu bleiben. Ein wichtiger Vorteil von WebAssembly ist die vorhersehbare Leistung, auch plattformübergreifend. Die strenge Typisierung und die Low-Level-Architektur ermöglichen es dem Compiler, stärkere Garantien zu geben, sodass WebAssembly-Code nur einmal optimiert werden muss und immer den „schnellen Pfad“ verwendet.

Code für WebAssembly schreiben

Bisher haben wir C/C++-Bibliotheken in WebAssembly kompiliert, um ihre Funktionen im Web zu nutzen. Wir haben den Code der Bibliotheken nicht wirklich verändert, sondern nur kleine Mengen an C/C++ Code geschrieben, um eine Brücke zwischen dem Browser und der Bibliothek zu bilden. Diesmal ist unsere Motivation anders: Wir möchten etwas von Grund auf neu mit WebAssembly schreiben, damit wir die Vorteile von WebAssembly nutzen können.

WebAssembly-Architektur

Wenn Sie für WebAssembly programmieren, sollten Sie wissen, was WebAssembly eigentlich ist.

Zitat von WebAssembly.org:

Wenn Sie C- oder Rust-Code in WebAssembly kompilieren, erhalten Sie eine .wasm-Datei mit einer Moduldeklaration. Diese Deklaration besteht aus einer Liste von „Importen“, die das Modul von seiner Umgebung erwartet, einer Liste von Exporten, die dieses Modul dem Host zur Verfügung stellt (Funktionen, Konstanten, Speicherbereiche) und natürlich den tatsächlichen Binäranweisungen für die darin enthaltenen Funktionen.

Mir war das erst klar geworden, als ich mir das genauer angesehen habe: Der Stack, der WebAssembly zu einer „stackbasierten virtuellen Maschine“ macht, wird nicht im Speicherbereich gespeichert, den WebAssembly-Module verwenden. Der Stack ist vollständig VM-intern und für Webentwickler nicht zugänglich (außer über Entwicklertools). Daher ist es möglich, WebAssembly-Module zu schreiben, die überhaupt keinen zusätzlichen Arbeitsspeicher benötigen, und nur den VM-internen Stack verwenden.

In unserem Fall benötigen wir zusätzlichen Speicher, um beliebigen Zugriff auf die Pixel des Bildes zu ermöglichen und eine gedrehte Version dieses Bildes zu generieren. WebAssembly.Memory ist dafür da.

Speicherverwaltung

Wenn Sie zusätzlichen Arbeitsspeicher verwenden, müssen Sie diesen in der Regel auch verwalten. Welche Teile des Arbeitsspeichers werden verwendet? Welche sind kostenlos? In C gibt es beispielsweise die Funktion malloc(n), die einen Speicherplatz von n aufeinanderfolgenden Byte findet. Funktionen dieser Art werden auch als „Allocators“ bezeichnet. Natürlich muss die Implementierung des verwendeten Allocator in Ihrem WebAssembly-Modul enthalten sein, wodurch die Dateigröße erhöht wird. Die Größe und Leistung dieser Speicherverwaltungsfunktionen kann je nach verwendetem Algorithmus sehr unterschiedlich sein. Deshalb bieten viele Sprachen mehrere Implementierungen zur Auswahl („dmalloc“, „emmalloc“, „wee_alloc“ usw.).

In unserem Fall kennen wir die Abmessungen des Eingabebilds (und damit die Abmessungen des Ausgabebilds), bevor wir das WebAssembly-Modul ausführen. Hier sahen wir eine Chance: Normalerweise übergeben wir den RGBA-Puffer des Eingabebilds als Parameter an eine WebAssembly-Funktion und geben das gedrehte Bild als Rückgabewert zurück. Um diesen Rückgabewert zu generieren, müssten wir den Allocator verwenden. Da wir jedoch die Gesamtmenge des benötigten Arbeitsspeichers kennen (doppelt so groß wie das Eingabebild, einmal für die Eingabe und einmal für die Ausgabe), können wir das Eingabebild mit JavaScript in den WebAssembly-Speicher einfügen, das WebAssembly-Modul ausführen, um ein zweites, gedrehtes Bild zu generieren, und dann das Ergebnis mit JavaScript zurücklesen. Wir können ganz ohne Speicherverwaltung auskommen!

Du hast die Wahl

Wenn Sie sich die ursprüngliche JavaScript-Funktion ansehen, die wir in WebAssembly umwandeln möchten, sehen Sie, dass es sich um reinen Berechnungscode ohne JavaScript-spezifische APIs handelt. Daher sollte es relativ einfach sein, diesen Code in eine beliebige Sprache zu portieren. Wir haben drei verschiedene Sprachen evaluiert, die in WebAssembly kompiliert werden: C/C++, Rust und AssemblyScript. Die einzige Frage, die wir für jede der Sprachen beantworten müssen, lautet: Wie greifen wir auf den Rohspeicher zu, ohne die Funktionen zur Speicherverwaltung zu verwenden?

C und Emscripten

Emscripten ist ein C-Compiler für das WebAssembly-Ziel. Emscripten soll als Drop-in-Ersatz für bekannte C-Compiler wie GCC oder clang dienen und ist größtenteils flag-kompatibel. Dies ist ein zentraler Bestandteil der Mission von Emscripten, da es das Kompilieren vorhandener C- und C++-Codes in WebAssembly so einfach wie möglich machen möchte.

Der Zugriff auf den Rohspeicher ist in C von Natur aus vorgesehen und genau aus diesem Grund gibt es Zeiger:

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

Hier wird die Zahl 0x124 in einen Verweis auf ungesignierte 8‑Bit-Ganzzahlen (oder Bytes) umgewandelt. Dadurch wird die Variable ptr effektiv in ein Array umgewandelt, das an der Speicheradresse 0x124 beginnt und das wir wie jedes andere Array verwenden können. So können wir zum Lesen und Schreiben auf einzelne Byte zugreifen. In unserem Fall sehen wir uns einen RGBA-Puffer eines Bildes an, den wir neu anordnen möchten, um eine Drehung zu erzielen. Um ein Pixel zu verschieben, müssen wir tatsächlich vier aufeinanderfolgende Bytes gleichzeitig verschieben (ein Byte für jeden Kanal: R, G, B und A). Um dies zu vereinfachen, können wir ein Array von vorzeichenlosen 32-Bit-Ganzzahlen erstellen. Gemäß der Konvention beginnt unser Eingabebild bei Adresse 4 und unser Ausgabebild direkt nach dem Ende des Eingabebilds:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

Nachdem wir die gesamte JavaScript-Funktion in C portiert haben, können wir die C-Datei mit emcc kompilieren:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

Wie immer generiert emscripten eine Glue-Code-Datei namens c.js und ein Wasm-Modul namens c.wasm. Das WASM-Modul wird durch GZIP auf nur etwa 260 Byte komprimiert, während der Glue-Code nach GZIP etwa 3,5 KB groß ist. Nach ein wenig Tüfteln konnten wir den Glue-Code abschaffen und die WebAssembly-Module mit den Vanilla-APIs instanziieren. Dies ist häufig mit Emscripten möglich, solange Sie nichts aus der C-Standardbibliothek verwenden.

Rust

Rust ist eine neue, moderne Programmiersprache mit einem umfangreichen Typsystem, einer Laufzeit und einem Eigentumsmodell, das Arbeitsspeicher- und Thread-Sicherheit garantiert. Rust unterstützt WebAssembly auch als Kernfunktion und das Rust-Team hat dem WebAssembly-Ökosystem viele hervorragende Tools beigesteuert.

Eines dieser Tools ist wasm-pack von der Arbeitsgruppe Rustwasm. wasm-packwandelt Ihren Code in ein webfreundliches Modul um, das sofort mit Bundlern wie webpack verwendet werden kann. wasm-pack ist äußerst praktisch, funktioniert aber derzeit nur für Rust. Die Gruppe erwägt, die Unterstützung weiterer WebAssembly-Targeting-Sprachen hinzuzufügen.

In Rust entsprechen Slices den Arrays in C. Und genau wie in C müssen wir Slices erstellen, die unsere Startadressen verwenden. Das verstößt gegen das von Rust erzwungene Speichersicherheitsmodell. Um unser Ziel zu erreichen, müssen wir das Schlüsselwort unsafe verwenden, mit dem wir Code schreiben können, der nicht diesem Modell entspricht.

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

Rust-Dateien mit kompilieren

$ wasm-pack build

ergibt ein 7,6 KB großes WASM-Modul mit etwa 100 Byte Glue-Code (beides nach gzip).

AssemblyScript

AssemblyScript ist ein relativ junges Projekt, das als TypeScript-zu-WebAssembly-Compiler dienen soll. Es wird jedoch nicht einfach nur TypeScript verwendet. AssemblyScript verwendet die gleiche Syntax wie TypeScript, ersetzt jedoch die Standardbibliothek durch seine eigene. Die Standardbibliothek modelliert die Funktionen von WebAssembly. Das bedeutet, dass Sie nicht einfach TypeScript-Code in WebAssembly kompilieren können. Sie müssen jedoch keine neue Programmiersprache lernen, um WebAssembly zu schreiben.

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

Angesichts der geringen Typenoberfläche unserer rotate()-Funktion war es ziemlich einfach, diesen Code in AssemblyScript zu portieren. Die Funktionen load<T>(ptr: usize) und store<T>(ptr: usize, value: T) werden von AssemblyScript für den Zugriff auf Rohspeicher bereitgestellt. Um unsere AssemblyScript-Datei zu kompilieren, müssen wir nur das npm-Paket AssemblyScript/assemblyscript installieren und

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

AssemblyScript liefert uns ein WASM-Modul mit etwa 300 Byte und keinen Glue-Code. Das Modul funktioniert nur mit den Vanilla-WebAssembly-APIs.

WebAssembly-Forensik

Die 7,6 KB von Rust sind im Vergleich zu den beiden anderen Sprachen überraschend groß. Es gibt einige Tools im WebAssembly-Ökosystem, mit denen Sie Ihre WebAssembly-Dateien analysieren können (unabhängig von der Sprache, mit der sie erstellt wurden), um herauszufinden, was los ist, und um die Situation zu verbessern.

Twiggy

Twiggy ist ein weiteres Tool des WebAssembly-Teams von Rust, mit dem eine Reihe nützlicher Daten aus einem WebAssembly-Modul extrahiert werden. Das Tool ist nicht Rust-spezifisch und ermöglicht es Ihnen, beispielsweise den Aufrufgraphen des Moduls zu prüfen, nicht verwendete oder überflüssige Abschnitte zu ermitteln und herauszufinden, welche Abschnitte zur Gesamtdateigröße Ihres Moduls beitragen. Das geht mit dem Befehl top von Twiggy:

$ twiggy top rotate_bg.wasm
Screenshot: Twiggy-Installation

In diesem Fall stammt der Großteil unserer Dateigröße aus dem Zuordnungscode. Das war überraschend, da in unserem Code keine dynamischen Zuweisungen verwendet werden. Ein weiterer wichtiger Faktor ist ein Unterabschnitt „Funktionsnamen“.

wasm-strip

wasm-strip ist ein Tool aus dem WebAssembly Binary Toolkit, kurz wabt. Es enthält verschiedene Tools, mit denen Sie WebAssembly-Module prüfen und bearbeiten können. wasm2wat ist ein Disassembler, der ein binäres WASM-Modul in ein für Menschen lesbares Format umwandelt. Wabt enthält außerdem wat2wasm, mit dem Sie dieses für Menschen lesbare Format wieder in ein binäres Wasm-Modul umwandeln können. Wir haben diese beiden ergänzenden Tools verwendet, um unsere WebAssembly-Dateien zu prüfen. Wir fanden wasm-strip jedoch am nützlichsten. wasm-strip entfernt unnötige Abschnitte und Metadaten aus einem WebAssembly-Modul:

$ wasm-strip rotate_bg.wasm

Dadurch wird die Dateigröße des Rustmoduls von 7,5 KB auf 6,6 KB (nach gzip) reduziert.

Wasm-Opt

wasm-opt ist ein Tool von Binaryen. Es nimmt ein WebAssembly-Modul und versucht, es sowohl hinsichtlich Größe als auch Leistung nur anhand des Bytecodes zu optimieren. Einige Tools wie Emscripten führen dieses Tool bereits aus, andere nicht. Es empfiehlt sich in der Regel, mithilfe dieser Tools zusätzliche Bytes zu sparen.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

Mit wasm-opt können wir noch ein paar Byte einsparen, sodass nach gzip insgesamt 6,2 KB übrig bleiben.

#![no_std]

Nach einigen Beratungen und Recherchen haben wir unseren Rust-Code ohne die Standardbibliothek von Rust neu geschrieben und dabei die Funktion #![no_std] verwendet. Dadurch werden auch die dynamischen Arbeitsspeicherzuweisungen vollständig deaktiviert, wodurch der Zuordnungscode aus unserem Modul entfernt wird. Kompilieren dieser Rust-Datei mit

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

führte nach wasm-opt, wasm-strip und gzip zu einem 1,6 KB großen WASM-Modul. Obwohl es immer noch größer als die von C und AssemblyScript generierten Module ist, ist es klein genug, um als Lightweight zu gelten.

Leistung

Bevor wir vorschnell Schlüsse aufgrund der Dateigröße ziehen: Wir haben diese Änderungen vorgenommen, um die Leistung zu optimieren, nicht die Dateigröße. Wie haben wir also die Leistung gemessen und was waren die Ergebnisse?

Benchmarking

Obwohl WebAssembly ein Low-Level-Bytecode-Format ist, muss es trotzdem durch einen Compiler gesendet werden, um hostspezifischen Maschinencode zu generieren. Genau wie JavaScript arbeitet der Compiler in mehreren Phasen. Einfach ausgedrückt: Die erste Phase ist viel schneller beim Kompilieren, generiert aber in der Regel langsameren Code. Sobald das Modul gestartet wird, beobachtet der Browser, welche Teile häufig verwendet werden, und sendet diese an einen optimierteren, aber langsameren Compiler.

Unser Anwendungsfall ist insofern interessant, dass der Code zum Drehen eines Bildes einmal, vielleicht zweimal verwendet wird. In den meisten Fällen können wir also nie die Vorteile des optimierten Compilers nutzen. Das ist wichtig, wenn Sie das Benchmarking berücksichtigen. Wenn wir unsere WebAssembly-Module 10.000 Mal in einer Schleife ausführen würden,wären die Ergebnisse unrealistisch. Um realistische Zahlen zu erhalten, sollten wir das Modul einmal ausführen und Entscheidungen anhand der Zahlen aus diesem einzelnen Durchlauf treffen.

Leistungsvergleich

Geschwindigkeitsvergleich nach Sprache
Geschwindigkeitsvergleich nach Browser

Diese beiden Diagramme sind unterschiedliche Ansichten derselben Daten. Im ersten Diagramm wird nach Browser, im zweiten nach verwendeter Sprache verglichen. Ich habe eine logarithmische Zeitskala gewählt. Außerdem ist es wichtig, dass für alle Benchmarks dasselbe 16-Megapixel-Testbild und dasselbe Hostgerät verwendet wurden, mit Ausnahme eines Browsers, der nicht auf demselben Computer ausgeführt werden konnte.

Ohne eine zu große Analyse dieser Diagramme ist klar, dass wir unser ursprüngliches Leistungsproblem gelöst haben: Alle WebAssembly-Module werden in ~500 ms oder weniger ausgeführt. Das bestätigt, was wir zu Beginn gesagt haben: WebAssembly bietet eine vorhersehbare Leistung. Unabhängig von der ausgewählten Sprache ist die Abweichung zwischen Browsern und Sprachen minimal. Genauer gesagt: Die Standardabweichung von JavaScript in allen Browsern beträgt etwa 400 ms, während die Standardabweichung aller unserer WebAssembly-Module in allen Browsern etwa 80 ms beträgt.

Aufwand

Ein weiterer Messwert ist der Aufwand, den wir für die Erstellung und Integration unseres WebAssembly-Moduls in Squoosh betreiben mussten. Es ist schwierig, dem Aufwand einen numerischen Wert zuzuweisen. Daher erstelle ich keine Grafiken. Ich möchte aber auf Folgendes hinweisen:

AssemblyScript lief reibungslos. Mit diesem Tool können Sie nicht nur TypeScript zum Schreiben von WebAssembly verwenden, was die Codeüberprüfung für meine Kollegen sehr einfach macht, sondern es werden auch klebstofffreie WebAssembly-Module erstellt, die sehr klein und leistungsstark sind. Die Tools in der TypeScript-Umgebung wie Prettier und Tslint werden wahrscheinlich einfach funktionieren.

Rust in Kombination mit wasm-pack ist ebenfalls sehr praktisch, eignet sich jedoch besser bei größeren WebAssembly-Projekten, da Bindungen und Arbeitsspeicherverwaltung erforderlich sind. Wir mussten ein wenig vom Happy Path abweichen, um eine wettbewerbsfähige Dateigröße zu erreichen.

C und Emscripten haben ein sehr kleines und leistungsstarkes WebAssembly-Modul erstellt, aber ohne den Mut, den Glue-Code auf das Nötigste zu reduzieren. Die Gesamtgröße (WebAssembly-Modul + Glue-Code) ist daher ziemlich groß.

Fazit

Welche Sprache sollten Sie also verwenden, wenn Sie einen JS-Hotpath haben und ihn schneller oder konsistenter mit WebAssembly machen möchten? Wie immer bei Leistungsfragen lautet die Antwort: Es kommt darauf an. Was haben wir also ausgeliefert?

Vergleichsgrafik

Wenn wir den Kompromiss zwischen Modulgröße und Leistung der verschiedenen von uns verwendeten Sprachen vergleichen, scheint entweder C oder AssemblyScript die beste Wahl zu sein. Wir haben uns entschieden, Rust zu veröffentlichen. Für diese Entscheidung gibt es mehrere Gründe: Alle bisher in Squoosh versendeten Codecs wurden mit Emscripten kompiliert. Wir wollten unser Wissen über das WebAssembly-System erweitern und in der Produktion eine andere Sprache verwenden. AssemblyScript ist eine gute Alternative, aber das Projekt ist noch relativ jung und der Compiler ist nicht so ausgereift wie der Rust-Compiler.

Auch wenn der Unterschied in der Dateigröße zwischen Rust und den anderen Sprachen im Streudiagramm ziemlich drastisch aussieht, ist er in Wirklichkeit nicht so groß: Das Laden von 500 B oder 1,6 KB dauert selbst über 2 Gbit/s weniger als ein Zehntel einer Sekunde. Und Rust wird diese Lücke in Bezug auf die Modulgröße hoffentlich bald schließen.

In Bezug auf die Laufzeitleistung erzielt Rust im Browser einen schnelleren Durchschnitt als AssemblyScript. Insbesondere bei größeren Projekten ist es mit Rust wahrscheinlicher, dass schneller Code generiert wird, ohne dass manuelle Codeoptimierungen erforderlich sind. Das sollte Sie jedoch nicht davon abhalten, das zu verwenden, mit dem Sie am besten vertraut sind.

Alles in allem war AssemblyScript eine tolle Entdeckung. So können Webentwickler WebAssembly-Module erstellen, ohne eine neue Sprache lernen zu müssen. Das AssemblyScript-Team ist sehr hilfsbereit und arbeitet aktiv an der Verbesserung seiner Toolchain. Wir werden AssemblyScript in Zukunft auf jeden Fall im Auge behalten.

Update: Rust

Nach der Veröffentlichung dieses Artikels wies uns Nick Fitzgerald aus dem Rust-Team auf sein hervorragendes Rust Wasm-Buch, das einen Abschnitt zur Optimierung der Dateigröße enthält. Durch das Befolgen der Anleitung dort (insbesondere durch die Aktivierung von Optimierungen zur Linkzeit und die manuelle Panikbehandlung) konnten wir „normalen“ Rust-Code schreiben und wieder Cargo (das npm von Rust) verwenden, ohne die Dateigröße zu vergrößern. Das Rust-Modul hat nach gzip eine Größe von 370 B. Weitere Informationen finden Sie in der PR, die ich bei Squoosh eingereicht habe.

Ein besonderer Dank geht an Ashley Williams, Steve Klabnik, Nick Fitzgerald und Max Graey für ihre Unterstützung auf diesem Weg.