เมื่อทํางานกับ WebAssembly คุณมักจะต้องดาวน์โหลดโมดูล คอมไพล์ อินสแตนซ์ แล้วใช้สิ่งที่ส่งออกใน JavaScript โพสต์นี้จะอธิบายแนวทางที่เราแนะนำเพื่อประสิทธิภาพสูงสุด
เมื่อทํางานกับ WebAssembly คุณมักจะต้องดาวน์โหลดโมดูล คอมไพล์ อินสแตนซ์ และจากนั้นใช้สิ่งที่ส่งออกใน JavaScript โพสต์นี้เริ่มต้นด้วยข้อมูลโค้ดทั่วไปที่ไม่ค่อยมีประสิทธิภาพมากนักซึ่งทําสิ่งดังกล่าว กล่าวถึงการเพิ่มประสิทธิภาพหลายอย่างที่เป็นไปได้ และสุดท้ายจะแสดงวิธีที่ง่ายที่สุดและมีประสิทธิภาพมากที่สุดในการเรียกใช้ WebAssembly จาก JavaScript
ข้อมูลโค้ดนี้จะดาวน์โหลด คอมไพล์ และสร้างอินสแตนซ์อย่างสมบูรณ์ แม้ว่าจะไม่ใช่วิธีที่มีประสิทธิภาพสูงสุดก็ตาม
อย่าใช้
(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);
})();
โปรดสังเกตว่าเราใช้ new WebAssembly.Module(buffer)
เพื่อเปลี่ยนบัฟเฟอร์การตอบกลับเป็นโมดูลอย่างไร ซึ่งเป็น API แบบซิงค์ ซึ่งหมายความว่าจะบล็อกเธรดหลักจนกว่าการดำเนินการจะเสร็จสมบูรณ์ Chrome จะปิดใช้ WebAssembly.Module
สำหรับบัฟเฟอร์ที่ใหญ่กว่า 4 KB เพื่อไม่ให้มีการใช้ หากต้องการหลีกเลี่ยงขีดจำกัดด้านขนาด เราอาจใช้ 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)
ยังคงไม่ใช่แนวทางที่เหมาะสมที่สุด แต่เราจะพูดถึงเรื่องนี้ในอีกสักครู่
ตอนนี้การดำเนินการเกือบทั้งหมดในสนิปตําแหน่งที่มีการแก้ไขเป็นแบบไม่พร้อมกันแล้ว ดังที่การใช้ await
แสดงให้เห็นอย่างชัดเจน ข้อยกเว้นเพียงอย่างเดียวคือ new WebAssembly.Instance(module)
ซึ่งมีขีดจำกัดขนาดบัฟเฟอร์ 4 KB เช่นเดียวกับใน Chrome เราสามารถใช้แบบไม่พร้อมกัน 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);
})();
มาพูดถึงการเพิ่มประสิทธิภาพ compile
ที่ฉันบอกใบ้ไปก่อนหน้านี้กัน เมื่อใช้การคอมไพล์แบบสตรีม เบราว์เซอร์จะเริ่มคอมไพล์โมดูล WebAssembly ได้ในขณะที่ยังดาวน์โหลดไบต์ของโมดูลอยู่ เนื่องจากการดาวน์โหลดและการคอมไพล์เกิดขึ้นพร้อมกัน วิธีนี้จึงเร็วกว่า โดยเฉพาะสำหรับเพย์โหลดขนาดใหญ่
หากต้องการเปิดใช้การเพิ่มประสิทธิภาพนี้ ให้ใช้ WebAssembly.compileStreaming
แทน WebAssembly.compile
การเปลี่ยนแปลงนี้ยังช่วยให้เรากำจัดบัฟเฟอร์อาร์เรย์กลางได้ด้วย เนื่องจากตอนนี้เราสามารถส่งผ่านอินสแตนซ์ Response
ที่ await fetch(url)
แสดงผลได้โดยตรง
(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
ได้อีกด้วย หากไม่จําเป็นต้องใช้ response
ในส่วนอื่นของโค้ด คุณสามารถส่ง Promise ที่ fetch
แสดงผลได้โดยตรงโดยไม่ต้อง await
ผลลัพธ์อย่างชัดเจน ดังนี้
(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
ในส่วนอื่นๆ ด้วย คุณก็ส่งผลลัพธ์นั้นโดยตรงได้ ดังนี้
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
แต่ส่วนตัวเราคิดว่าการแยกบรรทัดกันจะอ่านง่ายกว่า
มาดูกันว่าเราคอมไพล์คำตอบเป็นโมดูลและสร้างอินสแตนซ์ทันทีได้อย่างไร ปรากฏว่า WebAssembly.instantiate
สามารถคอมไพล์และสร้างอินสแตนซ์ได้ในครั้งเดียว โดย WebAssembly.instantiateStreaming
API จะทําดังนี้
(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);
})();
หากต้องการอินสแตนซ์เดียว ก็ไม่จำเป็นต้องเก็บออบเจ็กต์ module
ไว้ โค้ดจะง่ายขึ้นอีก
// 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);
})();
สรุปการเพิ่มประสิทธิภาพที่เราใช้ได้มีดังนี้
- ใช้ API แบบไม่พร้อมกันเพื่อหลีกเลี่ยงการบล็อกเธรดหลัก
- ใช้ Streaming API เพื่อคอมไพล์และสร้างอินสแตนซ์ของโมดูล WebAssembly ได้เร็วขึ้น
- อย่าเขียนโค้ดที่ไม่จําเป็น
สนุกไปกับ WebAssembly