Hot Path im JavaScript-Code der App durch WebAssembly ersetzen

Es ist durchweg schnell, ja

In meinen vorherigen Artikeln habe ich darüber gesprochen, wie WebAssembly es ermöglicht, die Bibliotheksumgebung von C/C++ ins Web zu bringen. Eine App, die C/C++-Bibliotheken in großem Umfang nutzt, ist squoosh. Mit unserer Webanwendung können Sie Bilder mit einer Vielzahl von Codecs komprimieren, die aus C++ in WebAssembly kompiliert wurden.

WebAssembly ist eine untergeordnete virtuelle Maschine, die den Bytecode ausführt, der in .wasm-Dateien gespeichert ist. Dieser Bytecode ist so stark typisiert und 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, bei der von Anfang an auf Sandboxing und Einbettung geachtet wurde.

Meiner Erfahrung nach werden die meisten Leistungsprobleme im Web durch ein erzwungenes Layout und übermäßig viele Painting verursacht, aber hin und wieder muss eine App 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 Bildzwischenspeicher um ein Vielfaches von 90 Grad dreht. OffscreenCanvas ist hierfür ideal geeignet, wird aber nicht von allen Browsern unterstützt, auf die wir ausgerichtet waren. Außerdem ist die Funktion in Chrome etwas ungenau.

Diese Funktion iteriert über jedes Pixel eines Eingabebildes und kopiert es an eine andere Position im Ausgabebild, um eine Rotation zu erreichen. Für ein Bild mit 4094 x 4096 Pixeln (16 Megapixel) wären über 16 Millionen Iterationen des inneren Codeblocks erforderlich, was wir als „Hot Path“ bezeichnen. Trotz der ziemlich hohen Anzahl von Iterationen beenden zwei von drei Browsern, die wir getestet haben, die Aufgabe in maximal 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;
    }
}

Bei einem Browser dauert das jedoch mehr als acht Sekunden. Die Art und Weise, wie Browser JavaScript optimieren, ist sehr kompliziert und verschiedene Engines optimieren auf unterschiedliche Dinge hin. Einige optimieren die Ausführung im Rohformat, andere für die Interaktion mit dem DOM. In diesem Fall sind wir in einem Browser auf einen nicht optimierten Pfad gestoßen.

WebAssembly hingegen basiert vollständig auf Rohausführungsgeschwindigkeit. Wenn wir für Code wie diesen also in allen Browsern eine schnelle, vorhersehbare Leistung wünschen, kann WebAssembly Abhilfe schaffen.

WebAssembly für vorhersehbare Leistung

Im Allgemeinen können JavaScript und WebAssembly die gleiche Spitzenleistung erreichen. Bei JavaScript ist diese Leistung jedoch nur auf dem „schnellen Pfad“ erreichbar und es ist oft schwierig, diesen „schnellen Pfad“ beizubehalten. Einer der Hauptvorteile von WebAssembly ist die vorhersehbare Leistung, sogar über verschiedene Browser hinweg. Die strikte Tipp- und Low-Level-Architektur ermöglicht es dem Compiler, starke Garantien zu geben, sodass WebAssembly-Code nur einmal optimiert werden muss und immer den „schnellen Pfad“ verwendet.

Für WebAssembly schreiben

Zuvor haben wir C/C++-Bibliotheken verwendet und in WebAssembly kompiliert, um ihre Funktionen im Web zu nutzen. Wir haben den Code der Bibliotheken nicht wirklich angepasst, sondern haben nur kleine Mengen C/C++-Code geschrieben, um die Brücke zwischen Browser und Bibliothek zu bilden. Diesmal hat sich unsere Motivation etwas geändert: 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, sollten Sie etwas mehr darüber erfahren, was WebAssembly eigentlich ist.

Um WebAssembly.org zu zitieren:

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.

Etwas, das mir nicht klar war, bis ich mir das angesehen habe: Der Stack, der WebAssembly zu einer „stackbasierten virtuellen Maschine“ macht, wird nicht in dem Speicherblock 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 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 unseres Bilds zu ermöglichen und eine gedrehte Version des Bildes zu generieren. Dazu ist WebAssembly.Memory gedacht.

Speicherverwaltung

Wenn zusätzlicher Arbeitsspeicher verwendet wird, muss er in der Regel irgendwie verwaltet werden. Welche Teile des Speichers werden verwendet? Welche sind kostenlos? In C haben Sie beispielsweise die Funktion malloc(n), die einen Speicherbereich von n aufeinanderfolgenden Byte findet. Funktionen dieser Art werden auch als "Zuordnungsoperatoren" bezeichnet. Natürlich muss die Implementierung des verwendeten Allocator in Ihrem WebAssembly-Modul enthalten sein, wodurch die Dateigröße erhöht wird. Größe und Leistung dieser Speicherverwaltungsfunktionen können je nach verwendetem Algorithmus erheblich variieren. Aus diesem Grund bieten viele Sprachen mehrere Implementierungen zur Auswahl ("dmalloc", "emmalloc", "wee_alloc" usw.).

In unserem Fall kennen wir die Abmessungen des Eingabebildes (und damit die Abmessungen des Ausgabebildes), bevor wir das WebAssembly-Modul ausführen. Hier sehen wir eine Möglichkeit: 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 jedoch wissen, wie viel Arbeitsspeicher insgesamt benötigt wird (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 JavaScript verwenden, um das Ergebnis zurückzulesen. Wir können auch ohne irgendwelche Arbeitsspeicherverwaltung wegkommen!

Du hast die Wahl!

Wenn Sie sich die ursprüngliche JavaScript-Funktion für WebAssembly-fy ansehen, sehen Sie, dass es sich um einen rein computergestützten Code ohne JavaScript-spezifische APIs handelt. Daher sollte es ziemlich einfach sein, diesen Code in eine beliebige Sprache zu portieren. Wir haben drei Sprachen ausgewertet, die zu 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 ohne Verwendung von Speicherverwaltungsfunktionen auf den Rohspeicher zu?

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 zu sein. Dies ist ein zentraler Bestandteil der Mission von Emscripten, da das Kompilieren vorhandener C- und C++-Code in WebAssembly so einfach wie möglich gemacht werden soll.

Der Zugriff auf den Roharbeitsspeicher liegt in der Natur von C und Zeiger sind genau aus diesem Grund vorhanden:

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 in ein Array umgewandelt, das bei der Speicheradresse 0x124 beginnt. Es kann wie jedes andere Array verwendet werden und ermöglicht den Zugriff auf einzelne Byte zum Lesen und Schreiben. In unserem Fall schauen wir uns einen RGBA-Zwischenspeicher eines Bildes an, das wir neu anordnen möchten, um eine Rotation zu erreichen. Um ein Pixel zu verschieben, müssen wir eigentlich 4 aufeinanderfolgende Bytes gleichzeitig verschieben (ein Byte für jeden Kanal: R, G, B und A). Zur Vereinfachung 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;
    }
}

Nachdem Sie die gesamte JavaScript-Funktion in C portiert haben, können Sie 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 auf ca.260 Byte komprimiert ist, während der Glue Code nach gzip etwa 3,5 KB groß ist. Nach einigen Tücken konnten wir den Glue Code entfernen und die WebAssembly-Module mit den einfachen 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, ohne Laufzeit und einem Inhabermodell, das Arbeitsspeicher- und Threadsicherheit garantiert. Rust unterstützt auch WebAssembly als Kernfunktion und das Rust-Team hat viele hervorragende Tools zur Entwicklung des WebAssembly-Ökosystems beigetragen.

Eines dieser Tools ist wasm-pack von der Arbeitsgruppe „rustwasm“. wasm-pack verwendet Ihren Code und verwandelt ihn in ein webfreundliches Modul, das mit Bundlern wie Webpack sofort einsatzbereit ist. wasm-pack ist sehr praktisch, funktioniert aber derzeit nur für Rust. Die Gruppe erwägt, weitere WebAssembly-Targeting-Sprachen zu unterstützen.

In Rust sind Slices das, was Arrays in C sind. Und genau wie in C müssen wir Slices mit den Startadressen erstellen. Dies verstößt gegen das von Rust erzwungene Speichersicherheitsmodell. Wir müssen also das Schlüsselwort unsafe verwenden, damit 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;
    }
}

Kompilieren der Rust-Dateien mit

$ wasm-pack build

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

AssemblyScript

AssemblyScript ist ein relativ junges Projekt, das ein TypeScript-to-WebAssembly-Compiler sein soll. Dabei ist jedoch zu beachten, dass nicht einfach nur TypeScript verarbeitet wird. AssemblyScript verwendet die gleiche Syntax wie TypeScript, ersetzt die Standardbibliothek jedoch selbst. Ihre Standardbibliothek modelliert die Funktionen von WebAssembly. Das bedeutet, dass Sie nicht einfach ein beliebiges TypeScript kompilieren können, das Sie in WebAssembly herumliegen, aber bedeutet es, 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, die unsere rotate()-Funktion hat, war es ziemlich einfach, diesen Code nach AssemblyScript zu portieren. Die Funktionen load<T>(ptr: usize) und store<T>(ptr: usize, value: T) werden von AssemblyScript bereitgestellt, um auf den Rohspeicher zuzugreifen. Um unsere AssemblyScript-Datei zu kompilieren, müssen wir nur das npm-Paket AssemblyScript/assemblyscript installieren und ausführen

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

AssemblyScript stellt uns ein Wasm-Modul von ca. 300 Byte und keinen Leimcode zur Verfügung. Das Modul funktioniert nur mit den einfachen WebAssembly-APIs.

WebAssembly-Forensik

Rust ist mit 7,6 KB eine überraschend große Größe im Vergleich zu den beiden anderen Sprachen. In der WebAssembly-Umgebung gibt es einige Tools, mit denen Sie Ihre WebAssembly-Dateien analysieren können (unabhängig von der Sprache, mit der sie erstellt wurden), und Ihnen mitteilen, wo das Problem liegt. Außerdem können Sie damit Ihre Situation verbessern.

Twiggy

Twiggy ist ein weiteres Tool des WebAssembly-Teams 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 die Aufrufgrafik des Moduls zu überprü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 wird deutlich, dass der Großteil der Dateigröße auf den Zuordnungsoperator zurückzuführen ist. Das war überraschend, da in unserem Code keine dynamischen Zuweisungen verwendet werden. Ein weiterer wichtiger Faktor ist der Unterabschnitt „Funktionsnamen“.

Wasm-Streifen

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

$ wasm-strip rotate_bg.wasm

Dadurch verringert sich die Dateigröße des Rost-Moduls von 7,5 KB auf 6,6 KB (nach gzip).

wasm-opt

wasm-opt ist ein Tool von Binaryen. Es nimmt ein WebAssembly-Modul und versucht, es sowohl in Bezug auf Größe als auch Leistung ausschließlich basierend auf dem Bytecode zu optimieren. Einige Tools wie Emscripten führen dieses Tool bereits aus, andere nicht. In der Regel 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 eine Handvoll Byte reduzieren, sodass nach gzip insgesamt 6,2 KB übrig bleiben.

#![no_std]

Nach einigen Beratungen und Recherchen haben wir unseren Rust-Code ohne Verwendung der Rust-Standardbibliothek mithilfe der Funktion #![no_std] neu geschrieben. Dadurch werden auch dynamische Arbeitsspeicherzuweisungen vollständig deaktiviert, sodass der Zuordnungscode aus unserem Modul entfernt wird. Wenn Sie diese Rust-Datei mit

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

Ergab ein 1,6 KB großes Wasm-Modul nach wasm-opt, wasm-strip und gzip. Es ist zwar immer noch größer als die von C und AssemblyScript generierten Module, ist aber klein genug, um als schlankes Element zu gelten.

Leistung

Bevor wir Schlussfolgerungen allein aufgrund der Dateigröße ziehen, haben wir uns zum Ziel gesetzt, die Leistung zu optimieren, nicht die Dateigröße. Wie haben wir also die Leistung gemessen und was waren die Ergebnisse?

Benchmarking durchführen

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 ausgedrückt: Die erste Phase dauert beim Kompilieren viel schneller, generiert aber tendenziell langsameren Code. Sobald das Modul ausgeführt wird, beobachtet der Browser, welche Teile häufig verwendet werden, und sendet diese durch einen optimierteren, aber langsameren Compiler.

Unser Anwendungsfall ist insofern interessant, als der Code zum Drehen eines Bildes ein- oder zweimal verwendet wird. In den meisten Fällen profitieren wir also nie von den Vorteilen des optimierenden Compilers. Dies ist beim Benchmarking wichtig. Wenn unsere WebAssembly-Module 10.000 Mal in einer Schleife ausgeführt werden,würde das zu unrealistischen Ergebnissen führen. Um realistische Zahlen zu erhalten, sollten wir das Modul einmal ausführen und Entscheidungen basierend auf den Zahlen aus diesem einzelnen Durchlauf treffen.

Leistungsvergleich

Geschwindigkeitsvergleich nach Sprache
Geschwindigkeitsvergleich pro Browser

Diese beiden Grafiken sind unterschiedliche Ansichten derselben Daten. Im ersten Diagramm vergleichen wir die verschiedenen Browser, in der zweiten Grafik die verwendeten Sprachen. Bitte beachten Sie, dass ich eine logarithmische Zeitskala ausgewählt habe. Es ist auch wichtig, dass alle Benchmarks dasselbe Testbild mit 16 Megapixel und denselben Hostcomputer verwendet haben, mit Ausnahme eines Browsers, der nicht auf demselben Computer ausgeführt werden konnte.

Ohne diese Diagramme zu ausführlich zu analysieren, ist klar, dass wir unser ursprüngliches Leistungsproblem gelöst haben: Alle WebAssembly-Module laufen in ca. 500 ms oder weniger. Dies bestätigt, was wir zu Beginn erläutert haben: WebAssembly bietet Ihnen eine vorhersehbare Leistung. Egal für welche Sprache wir uns entscheiden, die Unterschiede zwischen Browsern und Sprachen sind minimal. Genauer gesagt: Die Standardabweichung von JavaScript in allen Browsern beträgt etwa 400 ms. Die Standardabweichung aller WebAssembly-Module in allen Browsern beträgt etwa 80 ms.

Aufwand

Ein weiterer Messwert ist der Aufwand, den wir gesteckt haben, um unser WebAssembly-Modul zu erstellen und in Squoosh zu integrieren. Da es schwierig ist, dem Aufwand einen numerischen Wert zuzuweisen, erstelle ich keine Grafiken, aber es gibt ein paar Dinge, die ich hervorheben möchte:

AssemblyScript verlief reibungslos. Damit können Sie nicht nur TypeScript verwenden, um WebAssembly zu schreiben, was die Codeüberprüfung für meine Kollegen sehr einfach macht, sondern auch kleinfreie WebAssembly-Module erstellt, die sehr klein sind und eine angemessene Leistung bieten. Die Tools im TypeScript-System wie Prettier und tslint werden wahrscheinlich funktionieren.

Rust ist in Kombination mit wasm-pack ebenfalls sehr praktisch, eignet sich jedoch eher für größere WebAssembly-Projekte, da Bindungen erforderlich sind und Speicherverwaltung erforderlich ist. Wir mussten ein wenig vom Happy Path abweichen, um eine wettbewerbsfähige Dateigröße zu erreichen.

C und Emscripten haben sofort ein sehr kleines und leistungsstarkes WebAssembly-Modul erstellt, aber ohne den Mut, in Leimcode einzusteigen und ihn auf die absoluten Notwendigkeiten zu reduzieren. Die Gesamtgröße (WebAssembly-Modul + Klebecode) ist am Ende ziemlich groß.

Fazit

Welche Sprache sollten Sie also verwenden, wenn Sie einen JS Hot Path haben und diesen mit WebAssembly schneller oder einheitlicher machen möchten? Wie immer bei Fragen zur Leistung, lautet die Antwort: Es kommt darauf an. Was haben wir also versendet?

Vergleichsdiagramm

Ein Vergleich der Modulgröße und der Leistung der verschiedenen von uns verwendeten Sprachen macht aus, dass C oder AssemblyScript die beste Wahl ist. Wir haben uns entschieden, 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 die WebAssembly-Umgebung erweitern und in der Produktion eine andere Sprache verwenden. AssemblyScript ist eine starke Alternative, aber das Projekt ist noch relativ jung und der Compiler ist nicht so ausgereift wie der Rust-Compiler.

Im Streudiagramm sieht der Unterschied in der Dateigröße zwischen Rust und den anderen Sprachen zwar ziemlich stark aus, in der Realität ist das jedoch kein Problem: Das Laden von 500 B 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 weist Rust bei allen Browsern einen schnelleren Durchschnitt auf als AssemblyScript. Besonders bei größeren Projekten wird Rust eher schneller Code erstellen, ohne dass eine manuelle Codeoptimierung erforderlich ist. Das sollte Sie aber nicht davon abhalten, das zu verwenden, womit Sie sich am besten auskennen.

Trotzdem: AssemblyScript war eine großartige Entdeckung. Damit können Webentwickler WebAssembly-Module erstellen, ohne eine neue Sprache erlernen zu müssen. Das AssemblyScript-Team hat sehr schnell reagiert und arbeitet aktiv an der Verbesserung seiner Toolchain. Wir werden AssemblyScript in Zukunft auf jeden Fall im Auge behalten.

Update: Rost

Nach der Veröffentlichung dieses Artikels hat uns Nick Fitzgerald vom Rust-Team auf sein hervorragendes Rust Wasm-Buch aufmerksam gemacht, das einen Abschnitt zur Optimierung der Dateigröße enthält. Nachdem wir diese Anleitung befolgt haben (insbesondere die Optimierung der Linkzeit und manueller Panikbehandlung), konnten wir „normalen“ Rost-Code schreiben und wieder Cargo (das npm von Rust) verwenden, ohne die Dateigröße zu überlasten. Das Rust-Modul hat schließlich 370 B nach gzip. Weitere Informationen finden Sie in der PR-Datei, die ich auf Squoosh eröffnet habe.

Wir danken Ashley Williams, Steve Klabnik, Nick Fitzgerald und Max Graey für ihre Unterstützung auf diesem Weg.