W przypadku pracy z WebAssembly często konieczne jest pobranie modułu, skompilowanie go i utworzenie jego instancji, a potem wykorzystanie wyeksportowanych danych w języku JavaScript. W tym poście opisujemy zalecane przez nas metody pozwalające uzyskać optymalną skuteczność.
Pracując w WebAssembly, często chcesz pobrać, skompilować i utworzyć wystąpienie modułu a potem użyć tego, co wyeksportuje w JavaScript. Ten post rozpoczyna się od typowego, ale nieoptymalnego kodu robi to dokładnie, omawia kilka możliwych optymalizacji, a na koniec pokazuje najprostszy i najwydajniejszy sposób uruchamiania WebAssembly z poziomu JavaScriptu.
Ten fragment kodu przedstawia pełny taniec „download-build-instantiate”, ale nie jest to optymalny sposób:
Nie używaj tego!
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Zwróć uwagę, jak używamy new WebAssembly.Module(buffer)
do przekształcania bufora odpowiedzi w moduł. To jest
synchroniczny interfejs API, co oznacza, że blokuje wątek główny do momentu zakończenia procesu. Aby zniechęcić do jego używania, Chrome
wyłącza funkcję WebAssembly.Module
w przypadku buforów większych niż 4 KB. Aby obejść ten problem, możemy
użyj w zamian: await WebAssembly.compile(buffer)
:
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
await WebAssembly.compile(buffer)
to nadal nie jest optymalnym rozwiązaniem, ale zrobimy to za jakiś czas.
sekunda.
Prawie każda operacja w zmodyfikowanym fragmencie jest teraz asynchroniczna, ponieważ await
jasne. Jedynym wyjątkiem jest plik new WebAssembly.Instance(module)
, który ma taki sam bufor o wielkości 4 KB.
z ograniczeniem rozmiaru w Chrome. Dla spójności i zachowania głównego wątku
bezpłatne, możemy użyć asynchronicznego
WebAssembly.instantiate(module)
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Wróćmy do wspomnianej wcześniej optymalizacji compile
. Ze strumieniowaniem
kompilację danych, przeglądarka może już
zacznij kompilować moduł WebAssembly, gdy jego bajty są jeszcze pobierane. Od pobrania
i kompilacja odbywa się równolegle, co jest szybsze – zwłaszcza w przypadku dużych ładunków.
Aby włączyć tę optymalizację, użyj parametru WebAssembly.compileStreaming
zamiast WebAssembly.compile
.
Ta zmiana pozwala nam też pozbyć się bufora tablicy pośredniej, ponieważ możemy teraz przekazywać
Wystąpienie Response
zostało zwrócone bezpośrednio przez usługę await fetch(url)
.
(async () => {
const response = await fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(response);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Interfejs API WebAssembly.compileStreaming
akceptuje również obietnicę, która odnosi się do Response
instancji. Jeśli nie potrzebujesz kodu response
w innym miejscu w kodzie, możesz zlecić obietnicę
zwracany bezpośrednio przez funkcję fetch
, bez jawnego await
ingu swojego wyniku:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(fetchPromise);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Jeśli też nie potrzebujesz wyniku fetch
w innym miejscu, możesz go nawet przekazać bezpośrednio:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Osobiście uważam jednak, że lepiej jest umieścić go w osobnym wierszu.
Zobaczyć, jak kompilujemy odpowiedź w moduł, a następnie natychmiast tworzymy jej instancję? Jak się okazuje,
WebAssembly.instantiate
może jednocześnie kompilować i tworzyć instancje.
Interfejs API WebAssembly.instantiateStreaming
robi to w sposób strumieniowy:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
// To create a new instance later:
const otherInstance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Jeśli potrzebujesz tylko jednej instancji, nie ma sensu pozostawiania obiektu module
w pobliżu,
dalsze uproszczenie kodu:
// This is our recommended way of loading WebAssembly.
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Oto podsumowanie wprowadzonych przez nas optymalizacji:
- Używaj asynchronicznych interfejsów API, aby uniknąć blokowania wątku głównego
- Użycie interfejsów API strumieniowania do szybszego kompilowania i tworzenia instancji modułów WebAssembly
- Nie pisz kodu, którego nie potrzebujesz
Baw się z WebAssembly.