Hot Path im JavaScript-Code der App durch WebAssembly ersetzen

Es geht immer schnell.

In meinen vorherigen Artikeln habe ich darüber gesprochen, wie Sie mit WebAssembly das Bibliotheksökosystem von C/C++ im Web nutzen 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, in der Code ausgeführt werden kann, der von Anfang an in einer Sandbox und Einbettung berücksichtigt wurde.

Meiner Erfahrung nach werden die meisten Leistungsprobleme im Web durch erzwungenes Layout und übermäßiger Farbumfang verursacht. Ab und zu muss eine App jedoch eine rechenintensive Aufgabe ausführen, die viel Zeit in Anspruch nimmt. WebAssembly kann hier 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 schließen zwei von drei getesteten Browsern die Aufgabe innerhalb von maximal zwei Sekunden ab. Eine akzeptable Dauer für diese Art der 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 mehr als acht Sekunden. Die Art und Weise, wie Browser JavaScript optimieren, ist wirklich kompliziert und verschiedene Suchmaschinen nehmen verschiedene Optimierungen vor. Einige optimieren die Ausführung im Rohformat, andere für die Interaktion mit dem DOM. In diesem Fall erreichen wir einen nicht optimierten Pfad in einem Browser.

Bei WebAssembly hingegen geht es ausschließlich um Ausführungsgeschwindigkeiten. Wenn wir also für Code wie diesen browserübergreifend eine schnelle, vorhersehbare Leistung wünschen, kann WebAssembly dabei 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 browserübergreifend. Durch die strikte Typisierung und Low-Level-Architektur kann der Compiler stärker garantieren, dass der WebAssembly-Code nur einmal optimiert werden muss und immer den „schnellen Pfad“ verwendet.

Für WebAssembly schreiben

Zuvor 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 C/C++ Code geschrieben, um eine Brücke zwischen dem Browser und der Bibliothek zu bilden. Diesmal ist unsere Motivation anders: Wir möchten mit WebAssembly etwas von Grund auf neu schreiben, damit wir die Vorteile von WebAssembly nutzen können.

WebAssembly-Architektur

Wenn Sie für WebAssembly schreiben, ist es hilfreich zu verstehen, was WebAssembly eigentlich ist.

So zitieren Sie WebAssembly.org:

Wenn Sie einen C- oder Rust-Code in WebAssembly kompilieren, erhalten Sie eine .wasm-Datei, die eine Moduldeklaration enthält. Diese Deklaration besteht aus einer Liste von "Importen", die das Modul von seiner Umgebung erwartet, einer Liste der Exporte, die dieses Modul dem Host zur Verfügung stellt (Funktionen, Konstanten, Speicherblöcke) und natürlich den tatsächlichen binären Anweisungen für die darin enthaltenen Funktionen.

Mir war erst klar, als ich mir das genauer angesehen habe: Der Stack, der WebAssembly zu einer „stackbasierten virtuellen Maschine“ macht, ist nicht in dem Speicherblock gespeichert, den die 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. Dafür ist WebAssembly.Memory vorgesehen.

Speicherverwaltung

Wenn Sie zusätzlichen Speicher verwenden, wird es in der Regel erforderlich sein, diesen Speicher irgendwie zu verwalten. Welche Teile des Arbeitsspeichers werden verwendet? Welche davon sind kostenlos? In C haben Sie beispielsweise die Funktion malloc(n), die einen Speicherplatz von n aufeinanderfolgenden Byte ermittelt. 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. Diese Größe und Leistung dieser Speicherverwaltungsfunktionen kann je nach verwendetem Algorithmus erheblich variieren. 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 haben wir eine Chance gesehen: Traditionell würden wir den RGBA-Zwischenspeicher des Eingabebilds als Parameter an eine WebAssembly-Funktion übergeben und das gedrehte Bild als Rückgabewert zurückgeben. Um diesen Rückgabewert zu generieren, müssten wir den Allocator verwenden. Da wir aber wissen, wie viel Arbeitsspeicher insgesamt benötigt wird (doppelte Größe des Eingabebilds, einmal für die Eingabe und einmal für die Ausgabe), können wir das Eingabebild mit JavaScript in den WebAssembly-Speicher ablegen, das WebAssembly-Modul ausführen, um ein zweites, gedrehtes Bild zu generieren, und dann JavaScript verwenden, um das Ergebnis zurückzulesen. So kommen wir ohne Speicherverwaltung weg!

Du hast die Wahl

Wenn Sie sich die ursprüngliche JavaScript-Funktion ansehen, die mit WebAssembly optimiert werden soll, sehen Sie, dass es sich um einen reinen Rechencode ohne JavaScript-spezifische APIs handelt. Daher sollte es relativ einfach sein, diesen Code in eine beliebige Sprache zu portieren. Wir haben drei verschiedene Sprachen untersucht, 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 können wir auf Roharbeitsspeicher zugreifen, ohne Arbeitsspeicherverwaltungsfunktionen zu verwenden?

C und Emscripten

Emscripten ist ein C-Compiler für das WebAssembly-Ziel. Das Ziel von Emscripten ist es, als Drop-in-Ersatz für bekannte C-Compiler wie GCC oder Clang zu fungieren und größtenteils mit Flags 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 liegt in der Natur von C, und genau aus diesem Grund gibt es Hinweise:

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

Hier wandeln wir die Zahl 0x124 in einen Zeiger auf vorzeichenlose 8-Bit-Ganzzahlen (oder Byte) um. Dadurch wird die Variable ptr effektiv in ein Array beginnend mit der Speicheradresse 0x124, das wir wie jedes andere Array verwenden können, sodass wir zum Lesen und Schreiben auf einzelne Byte zugreifen können. In unserem Fall sehen wir uns den RGBA-Zwischenspeicher eines Bildes an, das wir neu anordnen möchten, um eine Rotation zu erzielen. Zum Verschieben eines Pixels müssen wir 4 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. Konventionsgemäß 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;
    }
}

Nach der Portierung der gesamten JavaScript-Funktion in C 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. Beachten Sie, dass das Wasm-Modul mit gzip nur ca.260 Byte groß ist, 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 auch WebAssembly als Hauptfunktion und das Rust-Team hat viele hervorragende Tools zum WebAssembly-Ökosystem beigetragen.

Eines dieser Tools ist wasm-pack von der Arbeitsgruppe Rustwasm. wasm-pack wandelt Ihren Code in ein für das Web geeignetes Modul um, das sofort mit Bundlern wie Webpack genutzt werden kann. wasm-pack ist sehr praktisch, funktioniert aber derzeit nur für Rust. Die Gruppe erwägt, die Unterstützung weiterer WebAssembly-Targeting-Sprachen hinzuzufügen.

In Rust sind Segmente das, was Arrays in C sind. Wie in C müssen wir Segmente erstellen, die unsere Startadressen verwenden. Dies verstößt gegen das Speichersicherheitsmodell, das Rust durchsetzt. Wir müssen also das Schlüsselwort unsafe verwenden, um Code zu schreiben, der diesem Modell nicht 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 kompilieren mit

$ wasm-pack build

ergibt ein 7,6 KB-Wasm-Modul mit etwa 100 Byte Glue Code (beide nach gzip).

AssemblyScript

AssemblyScript ist ein noch relativ neues Projekt, das als TypeScript-to-WebAssembly-Compiler dienen soll. Dabei ist jedoch zu beachten, dass damit nicht einfach nur TypeScript verwendet wird. AssemblyScript verwendet die gleiche Syntax wie TypeScript, ersetzt jedoch die Standardbibliothek durch seine eigene. Ihre Standardbibliothek modelliert die Funktionen von WebAssembly. Das bedeutet, dass Sie nicht einfach jedes in WebAssembly vorhandene TypeScript kompilieren können. Es bedeutet aber, dass Sie keine neue Programmiersprache erlernen müssen, 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 kleinen Schriftoberfläche der 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. Zum Kompilieren unserer AssemblyScript-Datei 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 ca. 300 Byte und keinen Glue Code. Das Modul funktioniert nur mit den einfachen WebAssembly-APIs.

WebAssembly-Forensik

Rust ist mit 7,6 KB im Vergleich zu den beiden anderen Sprachen überraschend groß. In der WebAssembly-Umgebung gibt es ein paar Tools, mit denen Sie Ihre WebAssembly-Dateien (unabhängig von der Sprache, mit der die Datei erstellt wurde) analysieren, Ihnen mitteilen, was vor sich geht, und mit denen Sie Ihre Situation verbessern können.

Twiggy

Twiggy ist ein weiteres Tool aus dem WebAssembly-Team von Rust, das eine Reihe aufschlussreicher Daten aus einem WebAssembly-Modul extrahiert. Das Tool ist nicht rust-spezifisch und ermöglicht es Ihnen, Dinge wie das Aufrufdiagramm des Moduls zu prüfen, nicht verwendete oder überflüssige Abschnitte zu ermitteln und herauszufinden, welche Abschnitte zur Gesamtdateigröße Ihres Moduls beitragen. Letzteres kann mit dem Twiggy-Befehl top ausgeführt werden:

$ twiggy top rotate_bg.wasm
Screenshot der Twiggy-Installation

In diesem Fall geht der Großteil unserer Dateigröße auf den Zuordnungscode zurück. Das war überraschend, da unser Code keine dynamischen Zuweisungen verwendet. Ein weiterer wichtiger Faktor ist ein Unterabschnitt „Funktionsnamen“.

Wasm-Streifen

wasm-strip ist ein Tool aus dem WebAssembly Binary Toolkit oder kurz „wabt“. Es enthält verschiedene Tools, mit denen Sie WebAssembly-Module prüfen und bearbeiten können. wasm2wat ist ein Disassemulationsmodul, das 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. wasm-strip ist unserer Meinung nach am hilfreichsten. 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 verwendet ein WebAssembly-Modul und versucht, es im Hinblick auf Größe und Leistung ausschließlich basierend auf dem Bytecode zu optimieren. Einige Tools wie Emscripten führen dieses Tool bereits aus, andere nicht. Üblicherweise empfiehlt es sich, mit diesen Tools zusätzliche Byte zu sparen.

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

Mit wasm-opt können wir weitere Byte kürzen, sodass nach gzip insgesamt 6,2 KB übrig bleiben.

#![no_std]

Nach einigen Recherchen und Recherchen haben wir unseren Rust-Code mit der Funktion #![no_std] umgeschrieben, ohne die Standardbibliothek von Rust zu verwenden. 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

liefert nach wasm-opt, wasm-strip und gzip ein 1,6 KB-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 uns allein auf Grundlage der Dateigröße zu Schlussfolgerungen machen, wollten wir nicht die Dateigröße, sondern die Leistung optimieren. Wie haben wir also die Leistung gemessen und was waren die Ergebnisse?

Benchmarks verwenden

Obwohl WebAssembly ein Low-Level-Bytecode-Format ist, muss es dennoch über einen Compiler gesendet werden, um hostspezifischen Maschinencode zu generieren. Genau wie JavaScript arbeitet der Compiler in mehreren Phasen. Einfach gesagt: Die Kompilierung in der ersten Phase geht viel schneller, generiert tendenziell jedoch langsameren Code. Sobald das Modul ausgeführt wird, beobachtet der Browser, welche Teile häufig verwendet werden, und sendet diese durch einen stärker optimierenden, 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 werden wir also niemals die Vorteile des optimierenden Compilers erhalten. Das ist wichtig, wenn Sie das Benchmarking berücksichtigen. Die Ausführung unserer WebAssembly-Module 10.000 Mal in einer Schleife würde zu unrealistischen Ergebnissen führen. Um realistische Zahlen zu erhalten, sollten wir das Modul einmal ausführen und anhand der Zahlen dieses einzelnen Durchlaufs Entscheidungen treffen.

Leistungsvergleich

Geschwindigkeitsvergleich nach Sprache
Geschwindigkeitsvergleich pro Browser

Diese beiden Grafiken sind unterschiedliche Ansichten derselben Daten. In der ersten Grafik vergleichen wir die Daten nach Browser, im zweiten nach der verwendeten Sprache. Bitte beachten Sie, dass ich eine logarithmische Zeitskala ausgewählt habe. Außerdem ist es wichtig, dass alle Benchmarks dasselbe 16-Megapixel-Testbild und denselben Hostcomputer verwendet haben, 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 alles, was wir zu Beginn festgelegt haben: WebAssembly bietet eine vorhersehbare Leistung. Egal für welche Sprache wir uns entscheiden, die Unterschiede zwischen Browsern und Sprachen sind minimal. Genau: Die Standardabweichung von JavaScript beträgt bei allen Browsern 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, der in die Erstellung und Integration unseres WebAssembly-Moduls in Squoosh investiert wurde. Es ist schwierig, dem Aufwand einen numerischen Wert zuzuweisen, daher erstelle ich keine Grafiken, aber ich möchte auf Folgendes hinweisen:

AssemblyScript lief reibungslos. Sie können mit TypeScript nicht nur WebAssembly schreiben, was die Codeüberprüfung für meine Kollegen sehr einfach macht, sondern auch klebefreie WebAssembly-Module erzeugt, die sehr klein sind und eine angemessene Leistung bieten. 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 Speicherverwaltung benötigt werden. Wir mussten ein wenig vom Happy Path abweichen, um eine wettbewerbsfähige Dateigröße zu erreichen.

C und Emscripten erstellten ein sehr kleines und leistungsstarkes WebAssembly-Modul, das sofort verfügbar war, aber ohne den Mut, in den Klebercode zu wechseln und ihn auf das Nötigste zu reduzieren, war die Gesamtgröße (WebAssembly-Modul + Glue Code) recht groß.

Fazit

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

Vergleichsdiagramm

Im Hinblick auf den Kompromiss zwischen Modulgröße und Leistung der verschiedenen verwendeten Sprachen ist C oder AssemblyScript die beste Wahl. Wir haben uns entschlossen, Rust zu versenden. 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 Größenunterschied zwischen Rust und den anderen Sprachen im Streudiagramm ziemlich drastisch ist, ist dies in Wirklichkeit nicht so groß: Das Laden von 500 Mrd.oder 1,6 KB selbst über 2G dauert weniger als eine Zehntelsekunde. Und Rust wird die 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. Besonders bei größeren Projekten ist Rust wahrscheinlicher, um schnelleren Code zu erstellen, ohne dass manuelle Codeoptimierungen erforderlich sind. Aber das hindert Sie nicht daran, das zu verwenden, womit Sie sich am besten auskennen.

Dennoch ist AssemblyScript eine großartige Entdeckung. Sie ermöglicht es Webentwicklern, WebAssembly-Module zu erstellen, ohne eine neue Sprache lernen zu müssen. Das AssemblyScript-Team war sehr reaktionsschnell und arbeitet aktiv an der Verbesserung seiner Toolchain. Wir werden AssemblyScript in Zukunft auf jeden Fall im Auge behalten.

Aktualisierung: Rost

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. Nach der dortigen Anleitung (insbesondere durch Aktivieren der Linkzeitoptimierung und manueller Panikbehandlung) konnten wir „normalen“ Rust-Code schreiben und Cargo (das npm von Rust) verwenden, ohne die Dateigröße zu überblähen. Das Rust-Modul hat nach gzip 370 B. Weitere Informationen finden Sie in der PR-Präsentation, die ich auf Squoosh eröffnet habe.

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