WebAssembly'den yararlanmak isteyen web geliştiricilerine yönelik bu kılavuzda CPU yoğun görevler için harici kaynakları kullanarak Wasm'ı yardımcı olabilir. Bu kılavuzda, başarılı bir proje için en iyi uygulamalardan derlemelerini ve örneklendirmelerini optimize etmek için Wasm modüllerini yükleyin. Google CPU'yu yoğun şekilde kullanan görevlerin web çalışanlarına nasıl kaydırılacağını uygulama kararlarıyla (ör. web sitesini ne zaman Çalışan ve projenin kalıcı olarak mı aktif olacağı, yoksa gerektiğinde hızlandırılıp artırılmayacağı. İlgili içeriği oluşturmak için kullanılan kılavuzu yinelemeli olarak geliştirir ve tek bir performans kalıbı sunar bu da sorun için en iyi çözümü önerene kadar devam eder.
Varsayımlar
CPU'yu çok yoğun şekilde gerektiren ve yalnızca dış kaynaklardan yararlanmak istediğiniz bir göreviniz olduğunu varsayın.
Yerele yakın performansı için WebAssembly'yi (Wasm) kullanın. CPU'yu yoğun olarak kullanan görev
bu kılavuzda örnek olarak verilen bir sayının faktöriyelini hesaplayabilirsiniz. İlgili içeriği oluşturmak için kullanılan
faktöriyel, bir tam sayının ve altındaki tüm tam sayıların çarpımıdır. Örneğin,
örneğin, dörtün faktöriyeli (4!
olarak yazılır) 24
değerine (yani,
4 * 3 * 2 * 1
). Rakamlar çok hızlı bir şekilde büyür. Örneğin, 16!
2,004,189,184
. CPU'yu yoğun şekilde kullanan bir görevin daha gerçekçi bir örneği
bir barkodu tarayarak veya
kafes resmini izleme.
factorial()
öğesinin, performans gösteren yinelemeli (yinelemeli değil) uygulaması
fonksiyonu, C++ dilinde yazılmış aşağıdaki kod örneğinde gösterilmektedir.
#include <stdint.h>
extern "C" {
// Calculates the factorial of a non-negative integer n.
uint64_t factorial(unsigned int n) {
uint64_t result = 1;
for (unsigned int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
}
Makalenin geri kalanı için derleme tabanlı bir Wasm modülü olduğunu
factorial.wasm
adlı dosyadaki Emscripten ile bu factorial()
işlevi
tümü kullanılıyor
en iyi kod optimizasyonu uygulamalarını inceleyin.
Bunun nasıl yapılacağıyla ilgili bilgilerinizi tazelemek için
ccall/cWrap kullanarak JavaScript'ten derlenmiş C işlevlerini çağırma
Aşağıdaki komut, factorial.wasm
dosyasını
bağımsız Wasm.
emcc -O3 factorial.cpp -o factorial.wasm -s WASM_BIGINT -s EXPORTED_FUNCTIONS='["_factorial"]' --no-entry
HTML'de, output
ile eşlenmiş input
içeren bir form
ve bir gönderme var
button
. Bu öğelere, adlarına göre JavaScript'ten referans verilir.
<form>
<label>The factorial of <input type="text" value="12" /></label> is
<output>479001600</output>.
<button type="submit">Calculate</button>
</form>
const input = document.querySelector('input');
const output = document.querySelector('output');
const button = document.querySelector('button');
Modülün yüklenmesi, derleme ve örneklendirmesi
Bir Wasm modülünü kullanabilmeniz için önce modülü yüklemeniz gerekir. Web'de bu durum
aracılığıyla
fetch()
API'ye gidin. Sizin de bildiğiniz gibi, web uygulamanız
İşlemciyi yoğun olarak kullanan bir görevse Wasm dosyasını mümkün olduğunca erken bir şekilde önceden yüklemeniz gerekir. Siz
bunu bir
CORS özellikli getirme
(uygulamanızın <head>
bölümünde).
<link rel="preload" as="fetch" href="factorial.wasm" crossorigin />
Aslında, fetch()
API eşzamansızdır ve await
işlemini
yardımcı olur.
fetch('factorial.wasm');
Ardından, Wasm modülünü derleyin ve örneklendirin. İrili ufaklı bir ada sahip olan
fonksiyonlar
WebAssembly.compile()
(artı
WebAssembly.compileStreaming()
)
ve
WebAssembly.instantiate()
kavramak yerine
WebAssembly.instantiateStreaming()
yöntemi, doğrudan akıştan bir Wasm modülü derler ve
fetch()
gibi temel kaynağa await
gerek yoktur. Bu, proje başlatma belgenizin
ve Wasm kodunun yüklenmesi için optimize edilmiş bir yöntem. Wasm modülünün
factorial()
işlevini hemen kullanabilirsiniz.
const importObject = {};
const resultObject = await WebAssembly.instantiateStreaming(
fetch('factorial.wasm'),
importObject,
);
const factorial = resultObject.instance.exports.factorial;
button.addEventListener('click', (e) => {
e.preventDefault();
output.textContent = factorial(parseInt(input.value, 10));
});
Görevi bir web çalışanına kaydırır
Bunu gerçekten CPU yoğun görevlerle ana iş parçacığında çalıştırırsanız uygulamanın tamamını engelliyor. Yaygın olarak kullanılan bir uygulama, bu tür görevleri bir web Çalışan.
Ana iş parçacığının yeniden yapılandırması
CPU'yu yoğun şekilde kullanan görevi bir web çalışanına taşımak için atılacak ilk adım, işlemi yeniden yapılandırmaktır.
takip edebilirsiniz. Ana ileti dizisi artık bir Worker
oluşturuyor ve bunun dışında
yalnızca girişin Web İşçisine gönderilmesi ve ardından girişin
ve o çıktıyı görüntüler.
/* Main thread. */
let worker = null;
// When the button is clicked, submit the input value
// to the Web Worker.
button.addEventListener('click', (e) => {
e.preventDefault();
// Create the Web Worker lazily on-demand.
if (!worker) {
worker = new Worker('worker.js');
// Listen for incoming messages and display the result.
worker.addEventListener('message', (e) => {
output.textContent = e.result;
});
}
worker.postMessage({ integer: parseInt(input.value, 10) });
});
Kötü: Görev Web Çalışanı'nda çalışıyor ancak kod müstehcen
Web çalışanı, Wasm modülünü örneklendirir ve bir mesaj aldıktan sonra
işlemi gerçekleştirir ve sonucu ana iş parçacığına geri gönderir.
Bu yaklaşımdaki sorun, bir Wasm modülünün mevcut durumla
WebAssembly.instantiateStreaming()
eşzamansız bir işlemdir. Bunun anlamı şudur:
olduğunu belirtmiyor. En kötü durumda ana iş parçacığı,
Web Worker henüz hazır değildir ve Web Worker, iletiyi hiçbir zaman almaz.
/* Worker thread. */
// Instantiate the Wasm module.
// 🚫 This code is racy! If a message comes in while
// the promise is still being awaited, it's lost.
const importObject = {};
const resultObject = await WebAssembly.instantiateStreaming(
fetch('factorial.wasm'),
importObject,
);
const factorial = resultObject.instance.exports.factorial;
// Listen for incoming messages, run the task,
// and post the result.
self.addEventListener('message', (e) => {
const { integer } = e.data;
self.postMessage({ result: factorial(integer) });
});
Daha iyi: Görev, Web Çalışanı'nda çalışır, ancak gereksiz yükleme ve derleme olabilir
Eşzamansız Wasm modülü örneklendirmesi sorununun geçici bir çözümü Wasm modülü yükleme, derleme ve örneklendirme işlemlerinin tamamını etkinliğe taşıyın ancak bu, söz konusu çalışmanın her eğitimde mesaj alındı. HTTP önbelleği ve HTTP önbelleği sayesinde derlenmiş Wasm bayt kodu. Bu en kötü çözüm değil, ancak daha iyi sağlar.
Eşzamansız kodu Web Çalışanı'nın başına değil, aslında verdiğimiz sözü yerine getirmek için beklemek yerine, verdiğiniz sözü değişkeni olduğunda, program hemen etkinliğin etkinlik işleyici bölümüne ve ana ileti dizisinden gelen hiçbir mesaj kaybolmaz. Etkinliğin iç kısmı bu durumda vaat beklenebilir.
/* Worker thread. */
const importObject = {};
// Instantiate the Wasm module.
// 🚫 If the `Worker` is spun up frequently, the loading
// compiling, and instantiating work will happen every time.
const wasmPromise = WebAssembly.instantiateStreaming(
fetch('factorial.wasm'),
importObject,
);
// Listen for incoming messages
self.addEventListener('message', async (e) => {
const { integer } = e.data;
const resultObject = await wasmPromise;
const factorial = resultObject.instance.exports.factorial;
const result = factorial(integer);
self.postMessage({ result });
});
İyi: Görev Web Çalışanı'nda çalışıyor ve yalnızca bir kez yüklenip derleniyor
Statik
WebAssembly.compileStreaming()
çözüme ulaştırılacak
WebAssembly.Module
.
Bu nesnenin güzel bir özelliği,
kullanabileceğiniz
postMessage()
.
Bu, Wasm modülünün ana modda yalnızca bir kez yüklenip derlenebileceği anlamına gelir.
iş parçacığı (hatta sadece yükleme ve derleme ile ilgilenen başka bir Web İşçisi bile),
ve ardından yoğun CPU kullanımından sorumlu Web Çalışanına aktarılır
görevi görebilir. Aşağıdaki kodda bu akış gösterilmektedir.
/* Main thread. */
const modulePromise = WebAssembly.compileStreaming(fetch('factorial.wasm'));
let worker = null;
// When the button is clicked, submit the input value
// and the Wasm module to the Web Worker.
button.addEventListener('click', async (e) => {
e.preventDefault();
// Create the Web Worker lazily on-demand.
if (!worker) {
worker = new Worker('worker.js');
// Listen for incoming messages and display the result.
worker.addEventListener('message', (e) => {
output.textContent = e.result;
});
}
worker.postMessage({
integer: parseInt(input.value, 10),
module: await modulePromise,
});
});
Web çalışanı tarafında ise geriye kalan tek şey WebAssembly.Module
öğesini ayıklamaktır.
ve örneklendiririm. WebAssembly.Module
içeren mesaj
akışı gerçekleştirildiğinde, Web Çalışanı'ndaki kod
WebAssembly.instantiate()
(önceki değer instantiateStreaming()
varyantına kıyasla). Örneklenen
modülü bir değişkende önbelleğe alınır, böylece örneklendirme çalışmasının yalnızca
bir kez de web’de çalışan bir ekiple çalışmaya başladım.
/* Worker thread. */
let instance = null;
// Listen for incoming messages
self.addEventListener('message', async (e) => {
// Extract the `WebAssembly.Module` from the message.
const { integer, module } = e.data;
const importObject = {};
// Instantiate the Wasm module that came via `postMessage()`.
instance = instance || (await WebAssembly.instantiate(module, importObject));
const factorial = instance.exports.factorial;
const result = factorial(integer);
self.postMessage({ result });
});
Mükemmel: Görev, satır içi Web İşçisinde çalışır ve yalnızca bir kez yüklenip derlenir
HTTP önbelleğinde bile olsa (ideal olarak) önbelleğe alınan Web Worker kodunu almak ve
ağa ulaşmak pahalıya mal olur. Performans konusunda yaygın olarak kullanılan püf noktalarından biri
Web Çalışanını satır içine alın ve blob:
URL'si olarak yükleyin. Bu da
örneklendirmek için Web İşçisine geçirilecek Wasm modülünü derledi
ana iş parçacığının bağlamları farklı olsa da
aynı JavaScript kaynak dosyasına dayalı olarak.
/* Main thread. */
const modulePromise = WebAssembly.compileStreaming(fetch('factorial.wasm'));
let worker = null;
const blobURL = URL.createObjectURL(
new Blob(
[
`
let instance = null;
self.addEventListener('message', async (e) => {
// Extract the \`WebAssembly.Module\` from the message.
const {integer, module} = e.data;
const importObject = {};
// Instantiate the Wasm module that came via \`postMessage()\`.
instance = instance || await WebAssembly.instantiate(module, importObject);
const factorial = instance.exports.factorial;
const result = factorial(integer);
self.postMessage({result});
});
`,
],
{ type: 'text/javascript' },
),
);
button.addEventListener('click', async (e) => {
e.preventDefault();
// Create the Web Worker lazily on-demand.
if (!worker) {
worker = new Worker(blobURL);
// Listen for incoming messages and display the result.
worker.addEventListener('message', (e) => {
output.textContent = e.result;
});
}
worker.postMessage({
integer: parseInt(input.value, 10),
module: await modulePromise,
});
});
Tembel veya istekli Web Çalışanı oluşturma
Şimdiye kadar tüm kod örnekleri Web Çalışanı'nı istek üzerine, diğer bir deyişle, görebilirsiniz. Uygulamanıza bağlı olarak daha istekli bir şekilde oluşturmalarını sağlar. Örneğin, uygulama boştayken veya hatta bir parçası haline geldi. Bu nedenle, Web Worker oluşturma işlemini taşıyın. kodunu düğmenin etkinlik işleyicisinin dışına çıkarmalıdır.
const worker = new Worker(blobURL);
// Listen for incoming messages and display the result.
worker.addEventListener('message', (e) => {
output.textContent = e.result;
});
Web çalışanını görev başında tutma veya göstermeme
Kendinize web çalışanını kullanmaya devam edip etmeyeceğiniz, veya ihtiyaç duyduğunuzda yeniden oluşturabilirsiniz. Her iki yaklaşım da avantaj ve dezavantajları var. Örneğin, bir web sitesinin Sürekli görevde bulunmak, uygulamanızın bellek ayak izini artırabilir ve eş zamanlı görevlerle uğraşmak daha zor olur. Çünkü, bir şekilde sonuçları bu isteklere geri dönüyor. Diğer yandan, Web Çalışanın önyükleme kodu oldukça karmaşık olabilir; bu nedenle her seferinde yeni bir metrik oluşturmanız gerekir. Neyse ki bunu kendi başınıza Böylece, User Timing API.
Şimdiye kadar kod örnekleri kalıcı bir Web Çalışanı tuttu. Aşağıdakiler kod örneği, gerektiğinde yeni bir Web Worker geçici olarak oluşturur. Şunların gerektiğini unutmayın: takip etmek için Web Çalışanı'nı feshetme kendiniz. (Kod snippet'i hata işlemeyi atlar, ancak (başarısız veya yanlış olması fark etmez), her durumda sonlandırın.)
/* Main thread. */
let worker = null;
const modulePromise = WebAssembly.compileStreaming(fetch('factorial.wasm'));
const blobURL = URL.createObjectURL(
new Blob(
[
`
// Caching the instance means you can switch between
// throw-away and permanent Web Worker freely.
let instance = null;
self.addEventListener('message', async (e) => {
// Extract the \`WebAssembly.Module\` from the message.
const {integer, module} = e.data;
const importObject = {};
// Instantiate the Wasm module that came via \`postMessage()\`.
instance = instance || await WebAssembly.instantiate(module, importObject);
const factorial = instance.exports.factorial;
const result = factorial(integer);
self.postMessage({result});
});
`,
],
{ type: 'text/javascript' },
),
);
button.addEventListener('click', async (e) => {
e.preventDefault();
// Terminate a potentially running Web Worker.
if (worker) {
worker.terminate();
}
// Create the Web Worker lazily on-demand.
worker = new Worker(blobURL);
worker.addEventListener('message', (e) => {
worker.terminate();
worker = null;
output.textContent = e.data.result;
});
worker.postMessage({
integer: parseInt(input.value, 10),
module: await modulePromise,
});
});
Demolar
Üzerinde oynayabileceğiniz iki demo var. Bir
anlık web işçisi
(kaynak kod)
diğerinde ise
kalıcı web işçisi
(kaynak kod).
Chrome Geliştirici Araçları'nı açıp Console'u kontrol ederseniz kullanıcı
Düğme tıklamasından tıklama işlemine kadar geçen süreyi ölçen Timing API günlükleri
ekranda gösterilen sonucu verir. Ağ sekmesinde blob:
URL'si gösterilir
istek. Bu örnekte, geçici ve kalıcı arasındaki zamanlama farkı
yaklaşık 3×. Pratikte, insan gözüne göre bu sistemde ikisi de birbirinden ayırt edilemez.
dava açın. Kendi gerçek yaşam uygulamanızın sonuçları büyük olasılıkla farklı olacaktır.
Sonuçlar
Bu gönderide, Wasm'i kullanırken yararlanabileceğiniz bazı performans kalıpları keşfediliyor.
- Genel bir kural olarak, akış yöntemlerini tercih edin
(
WebAssembly.compileStreaming()
veWebAssembly.instantiateStreaming()
) benzerlerine kıyasla (WebAssembly.compile()
veWebAssembly.instantiate()
) tıklayın. - Mümkünse performans ağırlıklı işleri bir web çalışanında dış kaynak kullanarak yaptırın ve Wasm
yükleme ve derleme, Web Worker'ın dışında yalnızca bir kez çalışır. Bu şekilde,
Web işçisinin, yalnızca ana makineden aldığı Wasm modülünü örneklendirmesi gerekir.
yükleme ve derlemenin gerçekleştiği
WebAssembly.instantiate()
: Bu durumda örneğin, şunları yaparsanız örnek önbelleğe alınabilir: her zaman erişilebilir olmasını sağlar. - Tek bir daimi Web Çalışanı bulundurmanın mantıklı olup olmadığını dikkatlice ölçün. veya ihtiyaç duyduklarında geçici Web İşçileri oluşturmak isteyebilirler. Ayrıca en uygun zamanın ne olduğunu düşünün. Dikkate alınması gereken noktalar bellek tüketimi, web çalışanı örneklendirme süresi, Ayrıca eşzamanlı isteklerle uğraşmanın karmaşıklığı da vardır.
Bu kalıpları göz önünde bulundurursanız optimum performans elde etmek için doğru yoldasınız demektir. Wasm performansı.
Teşekkür
Bu kılavuz, tarafından incelendi Andreas Haas, Jakob Kummerow, Deepti Gandluri, Alon Zakai, Furkan McCabe, François Beaufort ve Rachel Andrew.