Wykrywanie funkcji WebAssembly

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

Wersja WebAssembly 1.0 została wydana 4 lata temu, ale rozwój nie poprzestał na tym. Nowe funkcje są dodawane w ramach procesu standaryzacji oferty pakietowej. Tak jak w przypadku nowych funkcji w internecie, kolejność i terminy wdrożenia mogą znacznie się różnić w zależności od wyszukiwarki. Jeśli chcesz używać tych nowych funkcji, musisz zadbać o to, aby żaden z Twoich użytkowników nie został pominięty. Z tego artykułu dowiesz się, jak to zrobić.

Niektóre nowe funkcje zwiększają rozmiar kodu przez dodanie nowych instrukcji dotyczących typowych operacji, inne zaawansowane elementy podstawowe wydajności, a jeszcze inne ulepszają środowisko programistyczne i integrację z resztą sieci.

Pełną listę ofert pakietowych i ich etapów znajdziesz w oficjalnym repozytorium. Stan ich wdrażania w wyszukiwarkach znajdziesz na oficjalnej stronie z planem rozwoju 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ługiwanych przeglądarek. Następnie skompiluj bazę kodu oddzielnie dla każdej z tych grup. Na koniec po stronie przeglądarki musisz wykryć obsługiwane funkcje i wczytać odpowiedni pakiet JavaScript oraz Wasm.

Funkcje wyboru i grupowania

Omówmy te kroki, wybierając jako przykład zestaw dowolnych funkcji. Załóżmy, że w mojej bibliotece chcę używać obsługi SIMD, wątków i wyjątków ze względu na rozmiar i wydajność. ich przeglądarki są obsługiwane w ten sposób:

Tabela przedstawiająca obsługę wybranych funkcji w przeglądarce.
Wyświetl tę tabelę funkcji na stronie webassembly.org/roadmap.

Aby zadbać o optymalizację poszczególnych użytkowników, możesz podzielić przeglądarki na następujące kohorty:

  • Przeglądarki w Chrome: obsługiwane są wątki, karty SIMD i obsługa wyjątków.
  • Firefox: obsługiwane są interfejsy Thread i SIMD, obsługa wyjątków nie jest obsługiwana.
  • Safari: wątki są obsługiwane, obsługa SIMD i obsługa wyjątków nie jest obsługiwana.
  • Inne przeglądarki: zakładam, że obsługuje ona tylko podstawową obsługę WebAssembly.

Zestawienie jest podzielone według funkcji obsługi w najnowszej wersji każdej przeglądarki. Nowoczesne przeglądarki są ponadczasowe i automatycznie się aktualizują, więc w większości przypadków wystarczy Ci zadbać o najnowszą wersję. Jeśli jednak uwzględnisz podstawową kohortę WebAssembly jako kohortę zastępczą, możesz udostępnić działającą aplikację nawet użytkownikom z nieaktualnymi przeglądarkami.

Kompilowanie pod kątem różnych zbiorów cech

WebAssembly nie ma wbudowanego sposobu wykrywania obsługiwanych funkcji w czasie działania, więc wszystkie instrukcje w module muszą być obsługiwane w środowisku docelowym. Z tego względu dla każdego z tych zestawów funkcji musisz skompilować kod źródłowy w wamm.

Każdy łańcuch narzędzi i system kompilacji są inne. Aby ulepszyć działanie tych funkcji, zapoznaj się z dokumentacją własnego kompilatora. Aby uprościć ten proces, w poniższym przykładzie użyję biblioteki C++ z pojedynczym plikiem i pokażę, jak skompilować ją w Emscripten.

Wykorzystam SIMD w emulacji SSE2, wątki z obsługą biblioteki Pthreads oraz wybierz obsługę wyjątków Wasm lub zastępczą implementację 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ć elementów #ifdef __EMSCRIPTEN_PTHREADS__ i #ifdef __SSE2__, aby warunkowo wybierać między równoległymi implementacjami tych samych funkcji (w wątkach i SIMD) a implementacjami szeregowymi podczas kompilowania. 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 ich używać w taki sam sposób z C++, niezależnie od podstawowej implementacji wybranej za pomocą flag kompilacji.

Wczytuję prawidłowy pakiet

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. Łącząc go z dynamicznym importowaniem, możesz załadować 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

Ostatnie słowa

W tym poście pokazałam, jak wybierać i tworzyć pakiety różnych funkcji oraz przełączać się między nimi.

Wraz ze wzrostem liczby funkcji liczba kohort cech może stać się niemożliwa do obsługi. Aby rozwiązać ten problem, możesz wybrać kohorty funkcji na podstawie rzeczywistych danych o użytkownikach, pominąć mniej popularne przeglądarki i pozwolić im wybrać nieco mniej optymalne kohorty. Dopóki Twoja 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 mieć 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ą dostępną po wprowadzeniu minimalnej wersji produktu, którą trzeba wykryć i wczytać warunkowo, korzystając z podanego wyżej sposobu. Do tego momentu jedynym sposobem tworzenia i wczytywania kodu z wykorzystaniem nowych funkcji WebAssembly we wszystkich przeglądarkach jest ta metoda.