WebAssembly modüllerini verimli bir şekilde yükleme

WebAssembly ile çalışırken genellikle bir modülü indirmek, 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, en uygun olmayan şekilde olsa da indirme-derleme-oluşturma işleminin tamamını 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 işlemin tamamlanmasına kadar ana iş parçacığı engellenir. 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), hâlâ en uygun yaklaşım değildir ancak bu konuya birazdan değineceğiz.

Değiştirilen snippet'teki neredeyse her işlem artık await kullanımıyla açıkça anlaşılacağı üzere ayarsızdır. 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 değindiğim compile optimizasyonuna 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 yöntem, ö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, await fetch(url) tarafından döndürülen Response örneğini doğrudan iletebildiğimiz için ara dizi arabelleğinden de kurtulmamızı sağlar.

(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 bir söz 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 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);
})();

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

Yanıtı nasıl bir modüle derleyip hemen örneklendirdiğimizi görüyor musunuz? WebAssembly.instantiate, tek seferde derleyip örnekleyebiliyor. WebAssembly.instantiateStreaming API'si bunu akış şeklinde 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 örneklemek için akış API'lerini kullanma
  • İhtiyacınız olmayan kodlar yazmayın

WebAssembly ile eğlenin.