WebAssembly-Funktionserkennung

Hier erfahren Sie, wie Sie die neuesten WebAssembly-Funktionen nutzen und gleichzeitig Nutzer in allen Browsern unterstützen.

WebAssembly 1.0 wurde vor vier Jahren veröffentlicht, aber die Entwicklung endete nicht dort. Neue Funktionen werden durch den Standardisierungsprozess für Vorschläge hinzugefügt. Wie bei neuen Funktionen im Web können sich die Implementierungsreihenfolge und die Zeitpläne zwischen den verschiedenen Suchmaschinen erheblich unterscheiden. Wenn Sie diese neuen Funktionen nutzen möchten, müssen Sie dafür sorgen, dass keine Ihrer Nutzer ausgeschlossen werden. In diesem Artikel erfahren Sie, wie Sie das erreichen.

Einige neue Funktionen verbessern die Codegröße durch neue Anweisungen für gängige Vorgänge, andere bieten leistungsstarke Leistungsprimitive und wieder andere verbessern die Entwicklerfreundlichkeit und die Integration in das restliche Web.

Eine vollständige Liste der Vorschläge und ihrer jeweiligen Phasen finden Sie im offiziellen Repository. Auf der offiziellen Seite Feature Roadmap können Sie den Implementierungsstatus in den Engines verfolgen.

Damit Nutzer aller Browser Ihre Anwendung verwenden können, müssen Sie herausfinden, welche Funktionen Sie verwenden möchten. Unterteilen Sie sie dann in Gruppen basierend auf der Browserunterstützung. Kompilieren Sie dann Ihre Codebasis für jede dieser Gruppen separat. Schließlich müssen Sie auf Browserseite die unterstützten Funktionen erkennen und das entsprechende JavaScript- und Wasm-Bundle laden.

Elemente auswählen und gruppieren

Sehen wir uns diese Schritte an, indem wir ein beliebiges Feature-Set als Beispiel auswählen. Angenommen, ich habe festgestellt, dass ich aus Gründen der Größe und Leistung SIMD, Threads und die Ausnahmebehandlung in meiner Bibliothek verwenden möchte. Unterstützte Browser:

Eine Tabelle mit der Browserunterstützung der ausgewählten Funktionen.
Diese Funktionstabelle finden Sie unter webassembly.org/roadmap.

Sie können Browser in die folgenden Kohorten unterteilen, um für jeden Nutzer die bestmögliche Nutzererfahrung zu ermöglichen:

  • Chrome-basierte Browser: Threads, SIMD und die Ausnahmebehandlung werden unterstützt.
  • Firefox: Threads und SIMD werden unterstützt, die Ausnahmebehandlung nicht.
  • Safari: Threads werden unterstützt, SIMD und die Ausnahmebehandlung nicht.
  • Andere Browser: Es wird nur die grundlegende WebAssembly-Unterstützung vorausgesetzt.

Diese Aufschlüsselung erfolgt nach Funktionsunterstützung in der jeweils neuesten Version der einzelnen Browser. Moderne Browser sind zeitlos und werden automatisch aktualisiert. In den meisten Fällen müssen Sie sich also nur um die neueste Version kümmern. Solange Sie jedoch die WebAssembly-Baseline als Fallback-Kohorte angeben, können Sie auch Nutzern mit veralteten Browsern eine funktionierende Anwendung zur Verfügung stellen.

Für verschiedene Funktionsgruppen kompilieren

WebAssembly bietet keine integrierte Möglichkeit, unterstützte Funktionen zur Laufzeit zu erkennen. Daher müssen alle Anweisungen im Modul auf dem Ziel unterstützt werden. Daher müssen Sie den Quellcode für jede dieser verschiedenen Funktionsgruppen separat in Wasm kompilieren.

Jede Toolchain und jedes Build-System ist anders. Informationen dazu, wie Sie diese Funktionen anpassen, finden Sie in der Dokumentation Ihres eigenen Compilers. Zur Vereinfachung verwende ich im folgenden Beispiel eine C++-Bibliothek mit einer einzelnen Datei und zeige, wie sie mit Emscripten kompiliert wird.

Ich verwende SIMD über die SSE2-Emulation, Threads über die Pthreads-Bibliotheksunterstützung und wähle zwischen der Wasm-Ausnahmebehandlung und der Fallback-JavaScript-Implementierung:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main
.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main
.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main
.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main
.cpp -o main.basic.mjs -fexceptions

Im C++-Code selbst können #ifdef __EMSCRIPTEN_PTHREADS__ und #ifdef __SSE2__ verwendet werden, um bedingt zwischen parallelen (Threads und SIMD) Implementierungen derselben Funktionen und den seriellen Implementierungen zur Kompilierungszeit zu wählen. Das würde so aussehen:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
 
// …implementation using threads and SIMD for max speed
#else
 
// …implementation using threads but not SIMD
#endif
#else
 
// …fallback implementation for browsers without those features
#endif
}

Für die Ausnahmebehandlung sind keine #ifdef-Direktive erforderlich, da sie unabhängig von der über die Kompilierungsoptionen ausgewählten zugrunde liegenden Implementierung auf dieselbe Weise aus C++ verwendet werden kann.

Das richtige Bundle wird geladen

Nachdem Sie Bundles für alle Feature-Kohorten erstellt haben, müssen Sie das richtige aus der Haupt-JavaScript-Anwendung laden. Dazu müssen Sie zuerst ermitteln, welche Funktionen im aktuellen Browser unterstützt werden. Dazu können Sie die Bibliothek wasm-feature-detect verwenden. In Kombination mit dem dynamischen Import können Sie das optimierteste Bundle in jedem Browser laden:

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule
;
if (await threads()) {
 
if (await simd()) {
   
if (await exceptions()) {
      initModule
= import('./main.threads-simd-exceptions.mjs');
   
} else {
      initModule
= import('./main.threads-simd.mjs');
   
}
 
} else {
    initModule
= import('./main.threads.mjs');
 
}
} else {
  initModule
= import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

Abschließende Worte

In diesem Beitrag habe ich gezeigt, wie Sie Pakete für verschiedene Funktionsgruppen auswählen, erstellen und wechseln.

Wenn die Anzahl der Funktionen zunimmt,kann die Anzahl der Funktionskohorten unhandlich werden. Um dieses Problem zu vermeiden, können Sie Funktionskohorten basierend auf Ihren realen Nutzerdaten auswählen, die weniger beliebten Browser überspringen und auf etwas weniger optimale Kohorten zurückgreifen. Solange Ihre Anwendung weiterhin für alle Nutzer funktioniert, kann dieser Ansatz ein angemessenes Gleichgewicht zwischen progressiver Verbesserung und Laufzeitleistung bieten.

In Zukunft wird WebAssembly möglicherweise eine integrierte Möglichkeit haben, unterstützte Funktionen zu erkennen und innerhalb des Moduls zwischen verschiedenen Implementierungen derselben Funktion zu wechseln. Ein solcher Mechanismus wäre jedoch selbst eine Funktion nach dem MVP, die Sie mit dem oben beschriebenen Ansatz erkennen und bedingt laden müssten. Bis dahin ist dies die einzige Möglichkeit, Code mit neuen WebAssembly-Funktionen in allen Browsern zu erstellen und zu laden.