WebAssembly modüllerini verimli bir şekilde yükleme

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

WebAssembly ile çalışırken genellikle bir modülü indirmek, derlemek, örneklendirmek ve JavaScript'te dışa aktardıkları her şeyi kullanmak istersiniz. Bu yayın, tam olarak bunu yapan yaygın ancak uygun olmayan bir kod snippet'iyle başlıyor, olası birkaç optimizasyondan bahsediyor ve sonunda WebAssembly'yi JavaScript'ten çalıştırmanın en basit, en verimli yolunu gösteriyor.

Bu kod snippet'i, indirme-derleme ve başlatma dansını en uygun şekilde olmasa da eksiksiz bir ş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);
})();

Yanıt arabelleğini modüle dönüştürmek için new WebAssembly.Module(buffer) öğesini nasıl kullandığımıza dikkat edin. Bu eşzamanlı bir API'dir. Yani tamamlanana kadar ana iş parçacığını engeller. Chrome, kullanılmasını önlemek için 4 KB'tan büyük arabelleklerde WebAssembly.Module öğesini 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 değildir, ancak buna birazdan değineceğiz.

await kullanımının anlaşılması açıkça anlaşıldığı için, değiştirilen snippet'teki neredeyse her işlem artık eşzamansızdır. Bunun tek istisnası, Chrome'daki aynı 4 KB arabellek boyutu kısıtlamasına sahip olan new WebAssembly.Instance(module)'tir. Tutarlılık için ve ana iş parçacığını serbest tutmak amacıyla eşzamansız 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 bahsettiğim compile optimizasyonuna dönelim. Akış derlemesi sayesinde tarayıcı, modül baytları indirilmeye devam ederken WebAssembly modülünü derlemeye başlayabilir. İndirme ve derleme işlemleri birbirine paralel olarak gerçekleştiğinden, özellikle büyük boyutlu dosyalarda bu işlem daha hızlıdır.

İndirme süresi WebAssembly modülünün derleme zamanından uzunsa WebAssembly.derStreaming(), derlemeyi son baytlar indirildikten hemen sonra bitirir.

Bu optimizasyonu etkinleştirmek için WebAssembly.compile yerine WebAssembly.compileStreaming kullanın. Artık await fetch(url) tarafından döndürülen Response örneğini doğrudan geçirebildiğimiz için bu değişiklik sayesinde 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 dönüşen bir taahhüdü de kabul eder. Kodunuzda başka bir yerde response için ihtiyacınız yoksa sonucunu açıkça await belirtmeden doğrudan fetch tarafından döndürülen sözü 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 sonucunu başka bir yerde de 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);
})();

Yine de bunu ayrı bir satırda saklamanın daha okunaklı olduğunu düşünüyorum.

Yanıtı nasıl bir modül halinde derlediğimizi ve sonra nasıl hemen örneklediğimizi görüyor musunuz? WebAssembly.instantiate, tek seferde derleyip örnek oluşturabilir. 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 kodu daha da basitleştirerek module nesnesini tutmanın bir anlamı yoktur:

// 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 kullanma
  • WebAssembly modüllerini daha hızlı derlemek ve örneklendirmek için akış API'lerini kullanın
  • İhtiyacınız olmayan kodu yazmayın

WebAssembly ile iyi eğlenceler!