Khi làm việc với WebAssembly, bạn thường muốn tải một mô-đun xuống, biên dịch mô-đun đó, tạo bản sao mô-đun đó, sau đó sử dụng bất kỳ nội dung nào mà mô-đun đó xuất trong JavaScript. Bài đăng này giải thích phương pháp mà chúng tôi đề xuất để đạt được hiệu quả tối ưu.
Khi làm việc với WebAssembly, bạn thường muốn tải một mô-đun xuống, biên dịch mô-đun đó, tạo bản sao mô-đun đó rồi sử dụng bất kỳ nội dung nào mà mô-đun đó xuất trong JavaScript. Bài đăng này bắt đầu bằng một đoạn mã phổ biến nhưng không tối ưu, thực hiện chính xác việc đó, thảo luận một số cách tối ưu hoá có thể thực hiện và cuối cùng cho thấy cách đơn giản nhất, hiệu quả nhất để chạy WebAssembly từ JavaScript.
Đoạn mã này thực hiện toàn bộ quá trình tải xuống-biên dịch-tạo bản sao, mặc dù không tối ưu:
Đừng sử dụng phương thức này!
(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);
})();
Lưu ý cách chúng ta sử dụng new WebAssembly.Module(buffer)
để biến vùng đệm phản hồi thành một mô-đun. Đây là một API đồng bộ, có nghĩa là API này sẽ chặn luồng chính cho đến khi hoàn tất. Để hạn chế việc sử dụng, Chrome sẽ tắt WebAssembly.Module
đối với các vùng đệm lớn hơn 4 KB. Để giải quyết giới hạn về kích thước, chúng ta có thể sử dụng 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)
vẫn chưa phải là phương pháp tối ưu, nhưng chúng ta sẽ tìm hiểu về phương pháp này trong giây lát.
Giờ đây, hầu hết mọi thao tác trong đoạn mã đã sửa đổi đều không đồng bộ, vì việc sử dụng await
đã làm rõ điều này. Trường hợp ngoại lệ duy nhất là new WebAssembly.Instance(module)
, có cùng giới hạn kích thước vùng đệm 4 KB trong Chrome. Để đảm bảo tính nhất quán và giữ cho luồng chính luôn rảnh, chúng ta có thể sử dụng WebAssembly.instantiate(module)
không đồng bộ.
(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);
})();
Hãy quay lại với hoạt động tối ưu hoá compile
mà tôi đã gợi ý trước đó. Với tính năng biên dịch trực tuyến, trình duyệt có thể bắt đầu biên dịch mô-đun WebAssembly trong khi các byte mô-đun vẫn đang tải xuống. Vì quá trình tải xuống và biên dịch diễn ra song song, nên quá trình này sẽ nhanh hơn, đặc biệt là đối với các tải trọng lớn.
Để bật tính năng tối ưu hoá này, hãy sử dụng WebAssembly.compileStreaming
thay vì WebAssembly.compile
.
Thay đổi này cũng cho phép chúng ta loại bỏ vùng đệm mảng trung gian, vì giờ đây chúng ta có thể trực tiếp truyền thực thể Response
do await fetch(url)
trả về.
(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);
})();
API WebAssembly.compileStreaming
cũng chấp nhận một lời hứa phân giải thành một thực thể Response
. Nếu không cần response
ở nơi khác trong mã, bạn có thể trực tiếp truyền lời hứa do fetch
trả về mà không cần await
kết quả một cách rõ ràng:
(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);
})();
Nếu không cần kết quả fetch
ở nơi khác, bạn thậm chí có thể truyền trực tiếp kết quả đó:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Tuy nhiên, cá nhân tôi thấy dễ đọc hơn khi đặt mã này trên một dòng riêng.
Hãy xem cách chúng ta biên dịch phản hồi vào một mô-đun, sau đó tạo bản sao ngay lập tức. Hóa ra, WebAssembly.instantiate
có thể biên dịch và tạo bản sao cùng một lúc. API WebAssembly.instantiateStreaming
thực hiện việc này theo phương thức truyền trực tuyến:
(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);
})();
Nếu bạn chỉ cần một thực thể, thì không cần giữ lại đối tượng module
, giúp đơn giản hoá mã hơn nữa:
// 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);
})();
Các biện pháp tối ưu hoá mà chúng tôi áp dụng có thể được tóm tắt như sau:
- Sử dụng API không đồng bộ để tránh chặn luồng chính
- Sử dụng API truyền trực tuyến để biên dịch và tạo bản sao nhanh hơn cho các mô-đun WebAssembly
- Không viết mã bạn không cần
Chúc bạn vui vẻ với WebAssembly!