Wykrywanie funkcji WebAssembly

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

Wersja WebAssembly 1.0 została opublikowana 4 lata temu, ale programowanie nie zostało zatrzymane. Nowe funkcje są dodawane w ramach procesu standaryzacji oferty pakietowej. Tak jak w przypadku nowych funkcji w internecie, ich kolejność i harmonogram implementacji mogą się znacznie różnić w zależności od wyszukiwarki. Jeśli chcesz korzystać z tych nowych funkcji, musisz mieć pewność, że żaden z Twoich 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 dotyczących typowych operacji, inne dodają zaawansowane podstawowe elementy zwiększające wydajność, a inne zwiększają wygodę programistów i integrację z resztą sieci.

Pełną listę ofert i ich etapów znajdziesz w oficjalnym repozytorium. Możesz też śledzić stan ich implementacji w wyszukiwarkach na oficjalnej stronie z planem rozwoju funkcji.

Aby mieć pewność, że z Twojej aplikacji będą mogli korzystać użytkownicy wszystkich przeglądarek, musisz zdecydować, których funkcji chcesz używać. Następnie podziel je na grupy w zależności od 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.

Wybieranie i grupowanie funkcji

Omówmy te kroki, wybierając na przykład zestaw dowolnych funkcji. Załóżmy, że chcę używać karty SIM, wątków i obsługi wyjątków w bibliotece ze względu na rozmiar i wydajność. Ich obsługa przeglądarek jest następująca:

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

Możesz podzielić przeglądarki na następujące kohorty, aby zapewnić wszystkim użytkownikom optymalne wrażenia:

  • Przeglądarki Chrome: obsługiwane są wątki, SIMD i obsługa wyjątków.
  • Firefox: wątki i karty SIMD są obsługiwane, obsługa wyjątków nie jest obsługiwana.
  • Safari: wątki są obsługiwane, SIMD i wyjątki nie są obsługiwane.
  • Inne przeglądarki: załóżmy, że masz tylko bazową obsługę WebAssembly.

To zestawienie jest podzielone według obsługi 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 nie musisz przejmować się tylko najnowszą wersją. Jeśli jednak uwzględnisz podstawową wersję WebAssembly jako kohortę zastępczą, możesz udostępnić działającą aplikację nawet dla użytkowników z nieaktualnymi przeglądarkami.

Kompilowanie różnych zestawów funkcji

WebAssembly nie ma wbudowanego sposobu wykrywania obsługiwanych funkcji w czasie działania, dlatego wszystkie instrukcje w module muszą być obsługiwane w środowisku docelowym. Z tego powodu należy 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 dostosować te funkcje, zapoznaj się z dokumentacją własnego kompilatora. Dla uproszczenia w tym przykładzie użyję biblioteki C++ składającej się z jednego pliku i pokażę, jak ją skompilować w Emscripten.

Będę używać SIMD za pomocą emulacji SSE2 i wątków z biblioteką Pthreads oraz wybierać między obsługą wyjątków Wasm a zastępczą implementacją JavaScript:

# 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 korzystać z #ifdef __EMSCRIPTEN_PTHREADS__ i #ifdef __SSE2__, aby warunkowo wybierać między implementacjami równoległymi (wątkami i SIMD) tych samych funkcji a implementacjami szeregowymi 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
}

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

Wczytuję prawidłowy pakiet

Po utworzeniu pakietów dla wszystkich kohort funkcji musisz wczytać odpowiednią grupę z głównej aplikacji JavaScript. W tym celu najpierw sprawdź, które funkcje są obsługiwane w bieżącej przeglądarce. Aby to zrobić, użyj biblioteki wasm-feature-detect. Dzięki połączeniu z importowaniem dynamicznym 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 pokazaliśmy, jak wybierać pakiety funkcji, tworzyć je i przełączać się między nimi.

Wraz ze wzrostem liczby funkcji liczba kohort cech może stać się nieistotna. Aby rozwiązać ten problem, możesz wybrać kohorty cech na podstawie rzeczywistych danych użytkowników, pominąć mniej popularne przeglądarki i pozwolić im wrócić do nieco mniej optymalnych kohort. Jeśli Twoja aplikacja nadal działa dla wszystkich użytkowników, to podejście może zapewnić rozsądną równowagę między stopniowym ulepszaniem aplikacji a wydajnością działania.

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 MVP, którą trzeba by wykrywać i wczytywać warunkowo, korzystając z podanego wyżej sposobu. Do tego czasu jedynym sposobem kompilowania i wczytywania kodu we wszystkich przeglądarkach za pomocą nowych funkcji WebAssembly.