Wykrywanie funkcji WebAssembly

Dowiedz się, jak korzystać z najnowszych funkcji WebAssembly, obsługując użytkowników we wszystkich przeglądarkach.

Wersja 1.0 WebAssembly została wydana 4 lata temu, ale rozwój nie zatrzymał się na tym etapie. Nowe funkcje są dodawane w ramach procesu standaryzacji ofert. Jak to zwykle bywa w przypadku nowych funkcji w internecie, kolejność i terminy ich wdrażania mogą się znacznie różnić w zależności od silnika. Jeśli chcesz korzystać z tych nowych funkcji, musisz się upewnić, że żaden z użytkowników nie zostanie pominięty. Z tego artykułu dowiesz się, jak to zrobić.

Niektóre nowe funkcje poprawiają rozmiar kodu przez dodanie nowych instrukcji do typowych operacji, inne wprowadzają wydajne elementy, a jeszcze inne ułatwiają pracę deweloperom i integrację z resztą internetu.

Pełną listę propozycji i odpowiednich etapów znajdziesz w oficjalnym repozytorium. Stan ich implementacji w silnikach możesz śledzić na oficjalnej stronie mapy drogowej funkcji.

Aby użytkownicy wszystkich przeglądarek mogli korzystać z Twojej aplikacji, musisz określić, których funkcji chcesz użyć. Następnie podziel je na grupy na podstawie obsługi przez przeglądarki. Następnie skompiluj kod źródłowy osobno dla każdej z tych grup. Na koniec po stronie przeglądarki musisz wykryć obsługiwane funkcje i wczytać odpowiedni pakiet JavaScript i Wasm.

Wybieranie i grupowanie cech

Aby pokazać, jak to zrobić, weźmiemy pod uwagę dowolny zestaw funkcji. Załóżmy, że z powodu rozmiaru i wydajności chcę używać w swojej bibliotece instrukcji SIMD, wątków i obsługi wyjątków. Obsługa przeglądarek:

Tabela pokazująca obsługę wybranych funkcji przez przeglądarki.
Zobacz tę tabelę funkcji na stronie webassembly.org/roadmap.

Aby zapewnić użytkownikom optymalne wrażenia, możesz podzielić przeglądarki na te kohorty:

  • Przeglądarki oparte na Chrome: obsługiwane są wątki, instrukcje SIMD i obsługa wyjątków.
  • Firefox: obsługiwane są wątki i SIMD, ale nie obsługa wyjątków.
  • Safari: wątki są obsługiwane, ale SIMD i obsługa wyjątków nie są obsługiwane.
  • Inne przeglądarki: zakłada się, że obsługują one tylko podstawową wersję WebAssembly.

Ten podział uwzględnia obsługę funkcji w najnowszej wersji każdej przeglądarki. Nowoczesne przeglądarki są zawsze aktualne i automatycznie się aktualizują, więc w większości przypadków wystarczy Ci najnowsza wersja. Jeśli jednak uwzględnisz podstawowy WebAssembly jako grupę awaryjnych użytkowników, nadal będziesz mieć działającą aplikację nawet dla użytkowników ze starszymi przeglądarkami.

Kompilowanie dla różnych zestawów funkcji

WebAssembly nie ma wbudowanego sposobu wykrywania obsługiwanych funkcji w czasie wykonywania, dlatego wszystkie instrukcje w module muszą być obsługiwane na urządzeniu docelowym. W związku z tym musisz skompilować kod źródłowy w Wasm osobno dla każdego z tych zestawów funkcji.

Każda toolchain i każdy system kompilacji są inne, więc aby dowiedzieć się, jak je dostosować, musisz zapoznać się z dokumentacją własnego kompilatora. W tym przykładzie użyję dla uproszczenia biblioteki C++ w jednym pliku i pokażę, jak ją skompilować za pomocą Emscripten.

Użyję SIMD za pomocą emulacji SSE2, wątków za pomocą obsługi biblioteki Pthreads oraz obsługi wyjątków Wasm lub implementacji JavaScriptu:

# 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

Sam kod C++ może używać funkcji #ifdef __EMSCRIPTEN_PTHREADS__#ifdef __SSE2__, aby warunkowo wybierać implementacje tych samych funkcji (wątek i SIMD) i implementacje sekwencyjne w czasie kompilacji. Wygląda to tak:

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
}

Obsługa wyjątków nie wymaga dyrektyw #ifdef, ponieważ można jej używać w taki sam sposób w C++, niezależnie od implementacji wybranej za pomocą flag kompilacji.

Wczytywanie prawidłowego pakietu

Po utworzeniu pakietów dla wszystkich grup funkcji musisz wczytać odpowiedni z głównej aplikacji JavaScript. Aby to zrobić, najpierw sprawdź, które funkcje są obsługiwane w bieżącej przeglądarce. Możesz to zrobić za pomocą biblioteki wasm-feature-detect. Połączenie go z importem dynamicznym pozwala wczytać najbardziej zoptymalizowany pakiet w dowolnej przeglądarce:

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

Ostateczne słowa

W tym poście pokażę, jak wybierać, tworzyć i przełączać pakiety dla różnych zestawów funkcji.

Wraz ze wzrostem liczby funkcji może być niemożliwe utrzymanie odpowiedniej liczby kohorty funkcji. Aby rozwiązać ten problem, możesz wybrać grupy funkcji na podstawie rzeczywistych danych o użytkownikach, pominąć mniej popularne przeglądarki i przełączyć je na nieco mniej optymalne grupy. Jeśli aplikacja nadal działa dla wszystkich użytkowników, to podejście może zapewnić odpowiednią równowagę między ulepszaniem progresywnym a wydajnością w czasie działania.

W przyszłości WebAssembly może uzyskać wbudowany sposób wykrywania obsługiwanych funkcji i przełączania się między różnymi implementacjami tej samej funkcji w module. Taki mechanizm byłby jednak funkcją dodaną po wersji MVP, którą trzeba by wykrywać i wczytywać warunkowo, korzystając z powyższego podejścia. Do tego czasu to podejście pozostaje jedynym sposobem kompilowania i ładowania kodu przy użyciu nowych funkcji WebAssembly we wszystkich przeglądarkach.