เมื่อทำงานกับ 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 การคอมไพล์ WebAssembly.compatibleStreaming() จะทำให้การคอมไพล์เสร็จสิ้นเกือบจะทันทีหลังจากที่ดาวน์โหลดไบต์สุดท้ายแล้ว](https://web.dev/static/articles/loading-wasm/image/when-download-is-longer-48c21d175617c.png?authuser=6&hl=th)
หากต้องการเปิดใช้การเพิ่มประสิทธิภาพนี้ ให้ใช้ 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
ในที่อื่นๆ ในโค้ด คุณก็ส่งคำสัญญาที่ 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 แบบอะซิงโครนัสเพื่อหลีกเลี่ยงการบล็อกเทรดหลัก
- ใช้ API สตรีมมิงเพื่อคอมไพล์และสร้างอินสแตนซ์ของโมดูล WebAssembly ได้เร็วขึ้น
- อย่าเขียนโค้ดที่ไม่ต้องการ
สนุกกับ WebAssembly