WebAssembly modüllerini verimli bir şekilde yükleme

WebAssembly ile çalışırken genellikle bir modülü indirip derlemek, örneklemek ve ardından dışa aktardığı her şeyi JavaScript'te kullanmak istersiniz. Bu yayında, optimum verimlilik için önerdiğimiz yaklaşım açıklanmaktadır.

WebAssembly ile çalışırken genellikle bir modülü indirmek, derlemek, örneklemek ve ardından JavaScript'te dışa aktardığı her şeyi kullanmak istersiniz. Bu gönderi, tam olarak bunu yapan yaygın ancak en uygun kod snippet'iyle başlar, çeşitli olası optimizasyonları tartışır ve sonunda WebAssembly'i JavaScript'ten çalıştırmanın en basit ve en verimli yolunu gösterir.

Bu kod snippet'i, indirme-derleme-oluşturma işlemini en uygun olmayan şekilde gerçekleştirir:

Bunu kullanmayın.

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

Bir yanıt arabelleğini modüle dönüştürmek için new WebAssembly.Module(buffer)'ü nasıl kullandığımıza dikkat edin. Bu, senkronize bir API'dir. Yani, tamamlanana kadar ana iş parçacığını engeller. Chrome, kullanımını engellemek için 4 KB'tan büyük tamponlar için WebAssembly.Module'ü devre dışı bırakır. Boyut sınırını aşmak için bunun yerine await WebAssembly.compile(buffer) kullanabiliriz:

(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) hala ideal yaklaşım olmasa da birazdan bu konuyu ele alacağız.

await kullanımı açıkça görüldüğünden, değiştirilen snippet'teki neredeyse her işlem artık eşzamansız. Bunun tek istisnası, Chrome'dakiyle aynı 4 KB arabellek boyutu kısıtlamasına sahip olan new WebAssembly.Instance(module)'tir. Tutarlılık ve ana iş parçasını boş tutma için asenkron WebAssembly.instantiate(module) kullanabiliriz.

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

Daha önce ipucu verdiğim compile optimizasyonuna geri dönelim. Akışlı derleme sayesinde tarayıcı, modül baytları indirilirken bile WebAssembly modülünü derlemeye başlayabilir. İndirme ve derleme işlemi paralel olarak gerçekleştiğinden bu işlem, özellikle büyük yüklerde daha hızlıdır.

İndirme süresi, WebAssembly modülünün derleme süresinden uzun olduğunda WebAssembly.compileStreaming(), son baytlar indirildikten hemen sonra derlemeyi tamamlar.

Bu optimizasyonu etkinleştirmek için WebAssembly.compile yerine WebAssembly.compileStreaming kullanın. Bu değişiklik sayesinde await fetch(url) tarafından döndürülen Response örneğini doğrudan iletebildiğimiz için ara dizi arabelleğinden de kurtulabiliriz.

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

WebAssembly.compileStreaming API, Response örneğine çözümlenen taahhütleri de kabul eder. Kodunuzun başka bir yerinde response'ye ihtiyacınız yoksa sonucunu açıkça await etmeden fetch tarafından döndürülen vaadi doğrudan iletebilirsiniz:

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

fetch sonucuna başka bir yerde ihtiyacınız yoksa doğrudan iletebilirsiniz:

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

Ancak şahsen ayrı bir satırda tutmayı daha okunaklı buluyorum.

Yanıtı nasıl modül halinde derleyip ardından hemen örneklendirdiğimizi öğrenin. WebAssembly.instantiate tek seferde derleyip örneklendirebilir. WebAssembly.instantiateStreaming API, bunu akış yöntemiyle yapar:

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

Yalnızca tek bir örneğe ihtiyacınız varsa module nesnesini tutmanın bir anlamı yoktur. Bu durumda kodu daha da basitleştirebilirsiniz:

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

Uyguladığımız optimizasyonlar şu şekilde özetlenebilir:

  • Ana iş parçacığının engellenmesini önlemek için eşzamansız API'ler kullanın
  • WebAssembly modüllerini daha hızlı derlemek ve örneklendirmek için akış API'lerini kullanın.
  • İhtiyacınız olmayan kodlar yazmayın

WebAssembly ile eğlenin.