ข้ามไปยังเนื้อหา

Streaming Instantiation และ Fetch

ในแอปพลิเคชันจริง คุณจะไม่มี WAT source ในขณะ runtime — แต่จะ fetch ไฟล์ binary .wasm ที่คอมไพล์แล้วจาก server แทน API ที่แนะนำสำหรับวิธีนี้คือ WebAssembly.instantiateStreaming() ซึ่งเริ่มคอมไพล์ bytes ตั้งแต่เริ่มรับข้อมูลผ่านเครือข่าย แทนที่จะรอให้ดาวน์โหลดเสร็จสมบูรณ์ก่อน

WebAssembly.instantiate ต้องการให้ byte buffer ทั้งหมดอยู่ในหน่วยความจำก่อนจึงจะเริ่มคอมไพล์ได้ สำหรับ module ขนาดใหญ่ หมายความว่า browser จะนั่งรอระหว่างการดาวน์โหลด แล้วค่อยจ่ายต้นทุนการคอมไพล์ทั้งหมดในครั้งเดียว

WebAssembly.instantiateStreaming ส่ง bytes ให้คอมไพเลอร์ขณะที่กำลัง stream มาจากเครือข่าย การคอมไพล์และการดาวน์โหลดทับซ้อนกัน ทำให้ module พร้อมใช้งานได้เร็วขึ้น — ซึ่งเห็นได้ชัดโดยเฉพาะเมื่อการเชื่อมต่อช้าหรือสำหรับ module ที่มีขนาดหลายร้อยกิโลไบต์ขึ้นไป

// instantiate — waits for full download before compiling
const response = await fetch('/module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {});
// instantiateStreaming — compiles while bytes arrive
const { instance } = await WebAssembly.instantiateStreaming(
fetch('/module.wasm'),
{}
);

โปรดสังเกตว่า instantiateStreaming รับ Promise<Response> ที่ fetch คืนมาโดยตรง — ไม่จำเป็นต้อง await fetch ก่อน

WebAssembly.instantiateStreaming จะตรวจสอบ header Content-Type ของ response ก่อนเริ่ม stream bytes ไปยังคอมไพเลอร์ Server ต้อง ให้บริการไฟล์ .wasm ด้วย Content-Type: application/wasm หากส่ง type อื่น เช่น application/octet-stream API จะ reject ทันทีด้วย TypeError ก่อนที่จะคอมไพล์ bytes ใดๆ

เมื่อไม่สามารถควบคุมการตั้งค่า MIME type ของ server ได้ ให้ใช้รูปแบบสองขั้นตอนแทน:

async function loadWasm(url, imports) {
try {
return await WebAssembly.instantiateStreaming(fetch(url), imports);
} catch (e) {
if (e instanceof TypeError) {
// MIME type wrong — fall back to arrayBuffer
const response = await fetch(url);
const bytes = await response.arrayBuffer();
return WebAssembly.instantiate(bytes, imports);
}
throw e;
}
}

imports object ทำงานเหมือนกับที่ใช้ใน WebAssembly.instantiate ทุกประการ — เป็น argument ที่สอง และโครงสร้างต้องตรงกับ import section ของ module อย่างแม่นยำ

const result = await WebAssembly.instantiateStreaming(
fetch('/module.wasm'),
{ env: { log: n => console.log(n) } }
);

WebAssembly.Module ที่อยู่ใน result.module คือ compiled artifact ที่สามารถ serialize ได้ คุณสามารถเก็บไว้ใน IndexedDB เพื่อให้การเข้าชมครั้งถัดไปข้ามขั้นตอนการคอมไพล์ทั้งหมดและข้ามไปยัง instantiation ได้เลย:

const { module, instance } = await WebAssembly.instantiateStreaming(
fetch('/module.wasm'),
{}
);
// Store `module` in IndexedDB for next visit
// Later: const instance = await WebAssembly.instantiate(cachedModule, {});

Sandbox นี้ไม่มี server ให้ fetch ดังนั้นจึงไม่สามารถใช้ fetch() ได้ ตัวรันด้านล่างใช้ compileWat() เพื่อสร้าง compiled bytes แล้วเรียก WebAssembly.instantiate โดยตรง — ที่เป็นเส้นทางเดียวกับที่ streaming fallback จะใช้หลังจากได้รับ bytes แล้ว

WebAssembly
ตัวเลือกBenefitCost
instantiateStreamingเริ่มคอมไพล์ทันทีที่ bytes แรกมาถึง ลด time-to-interactive ได้ชัดเจนสำหรับ module ขนาดใหญ่ต้องพึ่ง server ส่ง header Content-Type: application/wasm ให้ถูกต้อง มิเช่นนั้น reject ด้วย TypeError
fallback ด้วย arrayBuffer() + instantiateทำงานได้แน่นอนแม้ควบคุม MIME type ของ server ไม่ได้เสียเวลารอดาวน์โหลดครบไฟล์ก่อนเริ่มคอมไพล์ ทำให้ startup ช้าลง
  • เรียก fetch() แล้ว await response.arrayBuffer() ก่อนเสมอโดยไม่ลอง instantiateStreaming ก่อน ทั้งที่ควรใช้ streaming เป็นค่าเริ่มต้นแล้ว fallback เฉพาะตอนจับ TypeError
  • ลืมตั้งค่า server (nginx, Express ฯลฯ) ให้ map นามสกุล .wasm เป็น application/wasm ทำให้ instantiateStreaming reject ทุกครั้งโดยไม่รู้สาเหตุ
  • ส่งข้อมูลก้อนใหญ่กลับไปมาผ่าน exports/imports ทีละ call หลัง instantiate สำเร็จ แทนที่จะใช้ shared linear memory เขียน-อ่านครั้งเดียว ทำให้เสีย throughput ที่ streaming ช่วยประหยัดมาไปเปล่าๆ

💡 ตัวอย่างจากของจริง

Squoosh ใช้ instantiateStreaming โหลด codec แต่ละตัวเพื่อให้ startup เร็วที่สุด ส่วน AutoCAD Web ก็อาศัย streaming instantiation ในการโหลด engine ขนาดใหญ่ ควบคู่กับ interop ที่มีประสิทธิภาพระหว่าง JS กับ Wasm เพื่อให้ผู้ใช้โต้ตอบกับโมเดล CAD ได้แบบ real-time

เหตุใด instantiateStreaming จึงเหมาะสมกว่า instantiate สำหรับ production?
Server ต้องใช้ MIME type อะไรเมื่อให้บริการไฟล์ .wasm สำหรับ instantiateStreaming?
instantiateStreaming จะ throw error อะไรหาก MIME type ไม่ถูกต้อง?