•7 min read

Giới thiệu về WebAssembly

Giới thiệu về WebAssembly

JavaScript đã là ngôn ngữ duy nhất mà các trình duyệt thực thi nguyên bản trong ba thập kỷ. WebAssembly đã thay đổi điều đó. Kể từ khi được tích hợp vào tất cả các trình duyệt lớn vào năm 2017, Wasm đã chuyển từ "thử nghiệm thú vị" thành một công cụ sản xuất được Figma, Google Earth, AutoCAD và FFmpeg.js sử dụng. Dưới đây là cách nó thực sự hoạt động.

Audio Briefing
0:00 / 0:00

WebAssembly là gì (và không phải là gì)

WebAssembly là một định dạng hướng dẫn nhị phân cho một máy ảo dựa trên stack. Bạn không viết Wasm bằng tay — bạn biên dịch từ C, C++, Rust, Go, hoặc hàng tá ngôn ngữ khác sang nó. Trình duyệt tải xuống tệp nhị phân .wasm, xác thực nó (nhanh chóng, vì nó là một định dạng được định nghĩa rõ ràng), và chạy nó.

Một vài điều Wasm không phải:

  • Nó không phải là một sự thay thế cho JavaScript. Wasm không thể trực tiếp chạm vào DOM. Nó cần JS để gọi document.getElementById và các hàm tương tự.
  • Nó không tự động nhanh hơn JS cho mọi thứ. Wasm thắng thế trong các tác vụ tính toán nặng, có thể dự đoán được. JS thường nhanh hơn cho việc thao tác DOM và xử lý sự kiện.
  • Nó không không an toàn. Wasm chạy trong một sandbox — nó chỉ có quyền truy cập vào bộ nhớ mà bạn cấp cho nó một cách rõ ràng.
Advertisement

Mô hình bộ nhớ: Bộ nhớ tuyến tính

Đây là khái niệm cốt lõi. Một module Wasm có quyền truy cập vào một bộ nhớ tuyến tính — một mảng byte phẳng mà nó hoàn toàn kiểm soát. Từ phía Wasm, nó trông giống như các offset i32 vào một ArrayBuffer. Từ phía JS, đó là cùng một buffer:

// Loading a .wasm module
const response = await fetch('/my-module.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.instantiate(buffer, {});
const { memory, add, allocate } = module.instance.exports;

// Read from Wasm memory
const view = new Uint8Array(memory.buffer);
console.log(view[0]);  // byte at offset 0

// Write into Wasm memory
view[100] = 42;

Module Wasm không thể truy cập bất cứ thứ gì bên ngoài buffer này. Không có window, không có document, không có hệ thống tệp. Sự cô lập này là nền tảng cho cả bảo mật và khả năng di động.

Biên dịch Rust sang Wasm: Con đường thực tế

Rust hiện có bộ công cụ Wasm tốt nhất trong số các ngôn ngữ. Crate wasm-bindgen tự động tạo ra mã kết nối JS/Wasm:

# Install target
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
// src/lib.rs
use wasm_bindgen::prelude::*;

// Exported to JavaScript
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => {
            let mut a = 0u64;
            let mut b = 1u64;
            for _ in 2..=n {
                let c = a + b;
                a = b;
                b = c;
            }
            b
        }
    }
}

// Pass and return strings
#[wasm_bindgen]
pub fn process_json(input: &str) -> String {
    // serde_json works in Wasm
    let v: serde_json::Value = serde_json::from_str(input)
        .unwrap_or(serde_json::Value::Null);
    format!("Processed: {} fields", v.as_object().map(|o| o.len()).unwrap_or(0))
}

Xây dựng và sử dụng:

wasm-pack build --target web

Điều này tạo ra một thư mục pkg/ với tệp .wasm của bạn, một trình bao bọc JS và các kiểu TypeScript. Nhập vào ứng dụng của bạn:

import init, { fibonacci, process_json } from './pkg/my_module.js';

await init(); // loads and instantiates the .wasm
console.log(fibonacci(40));  // 102334155, runs in ~0.1ms
console.log(process_json('{"a":1,"b":2}'));  // "Processed: 2 fields"

Biên dịch C/C++ với Emscripten

Đối với các codebase C/C++ hiện có, Emscripten là công cụ tiêu chuẩn:

# Install
git clone https://github.com/emscripten-core/emsdk.git
./emsdk/emsdk install latest && ./emsdk/emsdk activate latest
source ./emsdk/emsdk_env.sh
// image_process.c
#include <stdint.h>
#include <stdlib.h>

// Grayscale a pixel buffer in-place
void grayscale(uint8_t* data, int width, int height) {
    int num_pixels = width * height;
    for (int i = 0; i < num_pixels; i++) {
        int idx = i * 4;
        uint8_t r = data[idx], g = data[idx+1], b = data[idx+2];
        uint8_t gray = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b);
        data[idx] = data[idx+1] = data[idx+2] = gray;
    }
}
emcc image_process.c -o image_process.js \
  -s EXPORTED_FUNCTIONS='["_grayscale", "_malloc", "_free"]' \
  -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
  -O2

Sử dụng từ JavaScript:

const Module = await import('./image_process.js');
await Module.default();

const grayscale = Module.cwrap('grayscale', null, ['number', 'number', 'number']);

// Get pixel data from canvas
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Copy into Wasm memory
const ptr = Module._malloc(imageData.data.length);
Module.HEAPU8.set(imageData.data, ptr);

// Run C function
grayscale(ptr, canvas.width, canvas.height);

// Copy back
imageData.data.set(Module.HEAPU8.subarray(ptr, ptr + imageData.data.length));
ctx.putImageData(imageData, 0, 0);
Module._free(ptr);

Mô hình này — malloc, copy vào, tính toán, copy ra, free — là mô hình trao đổi dữ liệu Wasm cơ bản cho các ngôn ngữ không tích hợp GC.

Advertisement

Hiệu suất: Khi Wasm thực sự chiến thắng

Tác vụJSWasm (Rust)Tăng tốc
Fibonacci(40)0.8ms0.1ms8×
SHA-256 (1MB)14ms2.4ms5.8×
Giải mã JPEG (2MP)120ms22ms5.5×
Nối chuỗi (100k thao tác)2ms6ms0.3×
Cập nhật DOM (1000 nút)8ms40ms (qua cầu nối JS)0.2×

Mô hình rất rõ ràng: Wasm thắng thế trong các tác vụ tính toán nặng về CPU. Nó thua khi bạn thường xuyên vượt qua ranh giới JS/Wasm (mỗi lần vượt qua đều có chi phí) hoặc thực hiện các tác vụ DOM.

Gỡ lỗi Wasm

Chrome DevTools hỗ trợ gỡ lỗi Wasm trực tiếp. Đối với Rust, hãy thêm cờ wasm-pack build --dev để giữ các ký hiệu gỡ lỗi, sau đó trong DevTools → Sources, bạn sẽ thấy các tệp nguồn .rs của mình. Bạn có thể đặt các điểm dừng và kiểm tra các biến.

Đối với các bản dựng sản xuất, bản đồ nguồn được hỗ trợ nhưng các tệp nhị phân .wasm bị loại bỏ. Sử dụng wasm-opt -O3 từ bộ công cụ Binaryen để giảm kích thước tệp nhị phân (thường là 30-50%):

wasm-pack build --release
wasm-opt -O3 -o pkg/my_module_opt.wasm pkg/my_module_bg.wasm

Điều gì sẽ xảy ra tiếp theo: WASI

WebAssembly bắt đầu trong trình duyệt, nhưng WebAssembly System Interface (WASI) mở rộng nó sang các máy chủ. WASI cung cấp cho các module Wasm một cách tiêu chuẩn để truy cập các tệp, mạng và biến môi trường — trong khi vẫn giữ nguyên mô hình sandbox. Một module Wasm được biên dịch cho WASI có thể chạy trong Cloudflare Workers, Fermyon Spin, hoặc một tiến trình wasmtime cục bộ mà không cần biên dịch lại.

Wasm phía máy chủ là một chủ đề khác với Wasm trình duyệt, nhưng các nguyên tắc cơ bản — bộ nhớ tuyến tính, cô lập sandbox, khả năng di động nhị phân — là giống hệt nhau.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement