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, tạo thực thể cho mô-đun đó rồi sử dụng bất cứ nội dung nào xuất ra trong JavaScript. Bài đăng này giải thích phương pháp 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, tạo thực thể cho mô-đun đó và sau đó sử dụng bất kỳ nội dung nào xuất trong JavaScript. Bài đăng này bắt đầu bằng một mã phổ biến nhưng chưa tối ưu chính xác như vậy, thảo luận một số cách tối ưu hoá có thể có và cuối cùng cho thấy cách đơn giản, hiệu quả nhất để chạy WebAssembly từ JavaScript.
Đoạn mã này thực hiện vũ đạo tải xuống-biên dịch-tạo thực thể hoàn chỉnh, mặc dù theo cách không tối ưu:
Đừng sử dụng!
(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);
})();
Vui lòng lưu ý cách chúng ta sử dụng new WebAssembly.Module(buffer)
để chuyển vùng đệm phản hồi thành một mô-đun. Đây là một
đồng bộ, tức là chặn luồng chính cho đến khi hoàn tất. Để ngăn việc sử dụng phần mềm này, Chrome
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)
thay thế:
(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 không phải là phương pháp tối ưu, nhưng chúng ta sẽ tìm hiểu điều đó theo
giây.
Hầu hết mọi thao tác trong đoạn mã được sửa đổi hiện không đồng bộ, vì việc sử dụng await
tạo
rõ ràng. Trường hợp ngoại lệ duy nhất là new WebAssembly.Instance(module)
có cùng bộ đệm 4 KB
giới hạn kích thước trong Chrome. Để đảm bảo tính nhất quán và mục đích giữ lại luồng chính
miễn phí, nên chúng ta có thể sử dụng thuộc tính không đồng bộ
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);
})();
Hãy quay lại phương pháp tối ưu hoá compile
mà tôi gợi ý trước đó. Có chức năng phát trực tuyến
biên dịch ứng dụng, thì 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. Từ khi 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ể truyền
Thực thể Response
được await fetch(url)
trực tiếp 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 lời hứa sẽ phân giải thành Response
thực thể. Nếu không cần response
ở nơi khác trong mã, bạn có thể chuyển lời hứa
được fetch
trực tiếp trả về mà không cần await
rõ ràng kết quả:
(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);
})();
Mặc dù vậy, cá nhân tôi thấy bài viết này sẽ dễ đọc hơn nếu để trên một dòng riêng.
Xem cách chúng ta biên dịch phản hồi thành một mô-đun, sau đó tạo thực thể cho mô-đun đó ngay lập tức? Kết quả là,
WebAssembly.instantiate
có thể biên dịch và tạo thực thể chỉ trong một thao tác. Chiến lược phát hành đĩa đơn
API WebAssembly.instantiateStreaming
thực hiện việc này theo cách 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ể duy nhất, thì không cần phải giữ đối tượng module
xung quanh,
đơ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ó thể tóm tắt các biện pháp tối ưu hoá mà chúng tôi đã áp dụng 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 thực thể cho các mô-đun WebAssembly nhanh hơn
- Không viết mã bạn không cần
Hãy giải trí với WebAssembly!