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. Nowe funkcje są dodawane w ramach procesu standaryzacji oferty pakietowej. 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 ich 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żywać. 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. ich przeglądarki są obsługiwane w ten sposób:

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

Aby zapewnić każdemu użytkownikowi jak najlepsze wrażenia, możesz podzielić przeglądarki na te grupy:

  • 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, a obsługa wyjątków nie jest obsługiwana.
  • 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 pod kątem różnych zbiorów cech

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żdy łańcuch narzędzi i system kompilacji są inne. Aby ulepszyć działanie tych funkcji, zapoznaj 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 JavaScripta:

# 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 kohort funkcji musisz wczytać odpowiedni pakiet z głównej aplikacji JavaScript. Aby to zrobić, 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. Dopóki aplikacja nadal będzie działać u wszystkich użytkowników, takie podejście pozwoli zachować rozsądną równowagę między stopniowym ulepszaniem a wydajnością środowiska wykonawczego.

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 wykryć i wczytać 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.