การขยาย Memory และ Data Segment
module WebAssembly เริ่มต้นด้วยจำนวนหน้าที่คุณประกาศใน (memory N) แต่บางครั้งคุณต้องการ memory เพิ่มขณะ runtime — เช่น เมื่อผู้ใช้อัปโหลดไฟล์ขนาดใหญ่หรือข้อมูลของคุณเพิ่มขึ้นเกินที่คาดไว้ คำสั่ง memory.grow และ segment (data ...) ครอบคลุมสองเรื่องที่เกี่ยวข้องกัน: การขยาย memory และการเติม byte แบบ static ล่วงหน้า
คำสั่ง memory.grow
หัวข้อที่มีชื่อว่า “คำสั่ง memory.grow”memory.grow รับ argument i32 หนึ่งตัวจาก stack: จำนวนหน้าเพิ่มเติมที่ต้องการ allocate คืนค่าขนาด เดิม เป็นหน้า หรือ -1 ถ้า grow ล้มเหลว (เช่น หน่วยความจำไม่พอหรือเกิน maximum ที่ประกาศไว้)
(module (memory (export "mem") 1) (func (export "grow") (param $pages i32) (result i32) local.get $pages memory.grow))คุณยังสามารถ grow memory จากฝั่ง JavaScript ด้วย mem.grow(n):
const mem = instance.exports.mem;const oldPages = mem.grow(1); // returns previous page count, or -1Buffer detachment หลัง grow
หัวข้อที่มีชื่อว่า “Buffer detachment หลัง grow”นี่คือจุดสำคัญมากที่สุด: เมื่อ WebAssembly memory ขยาย ArrayBuffer พื้นฐานจะถูก detach typed view ใดก็ตามที่สร้างจาก buffer เดิมจะกลายเป็นแบบว่างเปล่าและใช้งานไม่ได้ คุณต้องสร้าง view ใหม่ทั้งหมดจาก mem.buffer ทันทีหลังจาก grow ทุกครั้ง
let view = new Uint8Array(mem.buffer); // valid before growmem.grow(1);// view is now detached — do NOT use itview = new Uint8Array(mem.buffer); // re-create from fresh bufferData segment
หัวข้อที่มีชื่อว่า “Data segment”Segment (data ...) ช่วยให้คุณฝัง byte แบบ static โดยตรงใน module และให้ byte เหล่านั้นถูกเขียนลง memory โดยอัตโนมัติตอน instantiation นี่คือวิธีที่ compiled language เริ่มต้น string literal และ read-only table
(module (memory (export "mem") 1) (data (i32.const 0) "Hello")) ;; writes 5 UTF-8 bytes at offset 0Byte จะถูก copy จาก module ลง memory ก่อนที่ฟังก์ชันใดจะรัน ดังนั้นข้อมูลจะพร้อมใช้งานทันทีเมื่อฟังก์ชัน export แรกถูกเรียก
ตัวอย่างด้านล่างใช้ segment (data ...) เขียน "Hi" ที่ offset 0 ขยาย memory หนึ่งหน้า สร้าง typed-array view ใหม่หลัง grow และตรวจสอบว่าทั้งขนาดใหม่และข้อมูลเดิมถูกต้อง
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
TypedArray view ที่สร้างจาก mem.buffer | เข้าถึง byte แบบ zero-copy รวดเร็ว ไม่มี overhead จากการ copy | ต้องสร้าง view ใหม่ทุกครั้งหลัง memory.grow เพราะ buffer เดิมถูก detach ไปแล้ว |
| จัดการ growth และ layout ของ memory เอง | คุมได้ว่าจะขยายเมื่อไหร่ ขนาดเท่าไหร่ | ไม่มี GC ช่วยตรวจ ต้อง track เองว่า pointer หรือ view ตัวไหนยังใช้งานได้อยู่หลัง grow |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- Cache typed-array view ไว้ก่อนเรียก
memory.growแล้วลืมสร้างใหม่ ทำให้ view ชี้ไปยัง buffer ที่ถูก detach ไปแล้ว อ่านหรือเขียนค่าผิดแบบเงียบๆ โดยไม่มี error เตือน - คิดว่า
memory.grow(n)รับจำนวน byte ทั้งที่จริงรับเป็นจำนวน page (แต่ละ page 64 KiB) ทำให้เผลอขอ memory เกินความจำเป็นไปมาก - วาง
(data ...)segment ที่ offset overlap กับพื้นที่ที่ JavaScript จะเขียนทับภายหลัง ทำให้ข้อมูลเริ่มต้นถูกเขียนทับโดยไม่ตั้งใจ
💡 ตัวอย่างจากของจริง
FFmpeg.wasm ใช้ linear memory buffer ที่ขยายได้ (
memory.grow) เพื่อ stream เฟรมวิดีโอที่ decode แล้วระหว่าง JavaScript กับ Wasm โดยตรง โดยไม่ต้อง copy เฟรมทั้งหมดกลับไปมาทุกครั้งที่หน่วยความจำไม่พอ