WebAssembly-Module effizient laden

Bei der Arbeit mit WebAssembly ist es oft sinnvoll, ein Modul herunterzuladen, zu kompilieren, zu instanziieren und anschließend in JavaScript zu exportieren. In diesem Beitrag wird unser empfohlenes Vorgehen für optimale Effizienz erläutert.

Bei der Arbeit mit WebAssembly möchten Sie oft ein Modul herunterladen, kompilieren, instanziieren und dann alle in JavaScript exportierten Daten verwenden. Dieser Beitrag beginnt mit einem gängigen, aber nicht optimalen Code Snippet, der genau das tut, erörtert mehrere mögliche Optimierungen und zeigt schließlich die ist die einfachste und effizienteste Methode, um WebAssembly aus JavaScript auszuführen.

Dieses Code-Snippet führt den gesamten Download, die Kompilierung und die Instanziierung des Tanzes durch, wenn auch nicht optimal:

Verwende das nicht!

(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);
})();

Wie Sie sehen, wird mit new WebAssembly.Module(buffer) ein Antwortpuffer in ein Modul umgewandelt. Dies ist ein synchrone API, d. h., der Hauptthread wird blockiert, bis er abgeschlossen ist. Um von dieser Verwendung abzuhalten, Deaktiviert WebAssembly.Module für Puffer, die größer als 4 KB sind. Um die Größenbeschränkung zu umgehen, Verwenden Sie stattdessen 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) ist immer noch nicht der optimale Ansatz, aber dazu kommen wir später noch. 2.

Nahezu jeder Vorgang im geänderten Snippet ist jetzt asynchron, da die Verwendung von await löschen. Die einzige Ausnahme ist new WebAssembly.Instance(module), das denselben Puffer von 4 KB hat. Größenbeschränkung in Chrome. Aus Gründen der Einheitlichkeit und um den Hauptthread beizubehalten kostenlos ist, können wir die asynchrone 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);
})();

Kehren wir zu der compile-Optimierung zurück, auf die ich vorhin schon hingewiesen habe. Mit Streaming kompilieren, kann der Browser bereits das WebAssembly-Modul zu kompilieren, während die Modulbyte noch heruntergeladen werden. Seit Download und die Kompilierung laufen parallel ab. Das geht schneller, insbesondere bei großen Nutzlasten.

Wenn die Downloadzeit um
länger als die Kompilierungszeit des WebAssembly-Moduls, dann WebAssembly.compileStreaming()
die Kompilierung fast unmittelbar nach dem Herunterladen der letzten Bytes beendet.

Um diese Optimierung zu aktivieren, verwenden Sie WebAssembly.compileStreaming anstelle von WebAssembly.compile. Diese Änderung ermöglicht uns auch, den Zwischen-Array-Zwischenspeicher zu entfernen, da wir nun den Array-Zwischenspeicher übergeben können. Response-Instanz, die von await fetch(url) direkt zurückgegeben wurde.

(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);
})();

Die WebAssembly.compileStreaming API akzeptiert auch ein Promise, das in ein Response aufgelöst wird. Instanz. Wenn response an anderer Stelle in Ihrem Code nicht benötigt wird, können Sie das Versprechen übergeben. von fetch direkt zurückgegeben, ohne das Ergebnis explizit mit await zu kennzeichnen:

(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);
})();

Wenn Sie das fetch-Ergebnis auch an anderer Stelle nicht benötigen, können Sie es sogar direkt übergeben:

(async () => {
  const module = await WebAssembly.compileStreaming(
    fetch('fibonacci.wasm'));
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

Ich persönlich finde es jedoch besser lesbar, wenn es in einer separaten Zeile steht.

Sehen Sie, wie wir die Antwort in einem Modul kompilieren und dann sofort instanziieren? Wie sich herausstellt, WebAssembly.instantiate kann auf einmal kompiliert und instanziiert werden. Die In der WebAssembly.instantiateStreaming API erfolgt dies in Form eines Streamings:

(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);
})();

Wenn Sie nur eine Instanz benötigen, ist es sinnlos, das module-Objekt griffbereit zu haben. Vereinfachen Sie den Code weiter:

// 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);
})();

Die von uns angewendeten Optimierungen lassen sich wie folgt zusammenfassen:

  • Asynchrone APIs verwenden, damit der Hauptthread nicht blockiert wird
  • Streaming-APIs verwenden, um WebAssembly-Module schneller zu kompilieren und zu instanziieren
  • Schreiben Sie keinen Code, den Sie nicht brauchen

Viel Spaß mit WebAssembly!