性能优化
WebAssembly 的主要优势是性能,本章介绍如何优化 WebAssembly 应用的性能。
编译优化
Rust 编译优化
在 Cargo.toml 中配置:
[profile.release]
opt-level = "s" # 优化体积(或 "z" 更激进)
lto = true # 链接时优化
codegen-units = 1 # 单代码生成单元(更好的优化)
strip = true # 移除符号信息
panic = "abort" # panic 时直接终止,减小体积
优化级别对比:
| 级别 | 说明 | 体积 | 速度 |
|---|---|---|---|
0 | 无优化 | 大 | 慢 |
1 | 基本优化 | 中 | 中 |
2 | 标准优化 | 中 | 快 |
3 | 最大速度优化 | 大 | 最快 |
s | 体积优化 | 小 | 中 |
z | 最大体积优化 | 最小 | 慢 |
Emscripten 编译优化
# 基本优化
emcc main.c -O2 -o main.html
# 最大速度优化
emcc main.c -O3 -o main.html
# 体积优化
emcc main.c -Os -o main.html
# 最大体积优化
emcc main.c -Oz -o main.html
# 链接时优化
emcc main.c -O3 -s WASM=1 -flto -o main.html
减小二进制体积
移除未使用代码
Rust:
[profile.release]
opt-level = "z"
lto = true
[dependencies]
wee_alloc = "0.4"
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
Emscripten:
emcc main.c -Oz -s DEAD_FUNCTIONS_ELIMINATION=1
禁用不需要的功能
Emscripten:
# 禁用文件系统
emcc main.c -s NO_FILESYSTEM=1
# 禁用异常处理
emcc main.c -s DISABLE_EXCEPTION_CATCHING=1
# 禁用 C++ RTTI
emcc main.cpp -fno-rtti
# 最小运行时
emcc main.c -s MINIMAL_RUNTIME=1
使用 Closure Compiler
emcc main.c --closure 1 -o main.html
压缩输出
# 使用 wasm-opt 优化
wasm-opt -Oz input.wasm -o output.wasm
# 使用 brotli 压缩
brotli output.wasm
减少边界跨越
JavaScript 和 WebAssembly 之间的调用有开销,应尽量减少跨边界调用。
批量处理
不好的做法:
for (let i = 0; i < 10000; i++) {
instance.exports.process_item(i);
}
好的做法:
const data = new Int32Array(memory.buffer, 0, 10000);
for (let i = 0; i < 10000; i++) {
data[i] = i;
}
instance.exports.process_batch(0, 10000);
使用内存共享
#[wasm_bindgen]
pub struct Processor {
buffer: Vec<i32>,
}
#[wasm_bindgen]
impl Processor {
#[wasm_bindgen(constructor)]
pub fn new(capacity: usize) -> Self {
Processor {
buffer: vec![0; capacity],
}
}
pub fn get_buffer_ptr(&mut self) -> *mut i32 {
self.buffer.as_mut_ptr()
}
pub fn process(&mut self, len: usize) {
for i in 0..len {
self.buffer[i] *= 2;
}
}
}
JavaScript 端:
const processor = new Module.Processor(10000);
const ptr = processor.get_buffer_ptr();
const offset = ptr / 4;
// 直接写入内存
const view = new Int32Array(memory.buffer);
for (let i = 0; i < 10000; i++) {
view[offset + i] = i;
}
// 一次调用处理
processor.process(10000);
内存优化
预分配内存
#[wasm_bindgen]
pub struct Buffer {
data: Vec<u8>,
}
#[wasm_bindgen]
impl Buffer {
#[wasm_bindgen(constructor)]
pub fn new(capacity: usize) -> Self {
Buffer {
data: Vec::with_capacity(capacity),
}
}
pub fn write(&mut self, data: &[u8]) {
self.data.extend_from_slice(data);
}
}
避免频繁分配
// 不好的做法
#[wasm_bindgen]
pub fn process(input: Vec<u8>) -> Vec<u8> {
input.iter().map(|&b| b * 2).collect()
}
// 好的做法:复用缓冲区
#[wasm_bindgen]
pub struct Processor {
buffer: Vec<u8>,
}
#[wasm_bindgen]
impl Processor {
pub fn process(&mut self, input: &[u8]) -> &[u8] {
self.buffer.clear();
self.buffer.extend(input.iter().map(|&b| b * 2));
&self.buffer
}
}
使用对象池
use std::collections::VecDeque;
pub struct Pool<T> {
items: VecDeque<T>,
factory: fn() -> T,
}
impl<T> Pool<T> {
pub fn new(capacity: usize, factory: fn() -> T) -> Self {
let mut items = VecDeque::with_capacity(capacity);
for _ in 0..capacity {
items.push_back(factory());
}
Pool { items, factory }
}
pub fn acquire(&mut self) -> T {
self.items.pop_front().unwrap_or_else(|| (self.factory)())
}
pub fn release(&mut self, item: T) {
self.items.push_back(item);
}
}
SIMD 优化
SIMD(单指令多数据)可以并行处理多个数据。
Rust SIMD
#[cfg(target_arch = "wasm32")]
use std::arch::wasm32::*;
#[wasm_bindgen]
pub fn add_arrays_simd(a: &[f32], b: &[f32], result: &mut [f32]) {
let chunks = a.len() / 4;
for i in 0..chunks {
let offset = i * 4;
unsafe {
let va = v128_load(a.as_ptr().add(offset) as *const v128);
let vb = v128_load(b.as_ptr().add(offset) as *const v128);
let sum = f32x4_add(va, vb);
v128_store(result.as_mut_ptr().add(offset) as *mut v128, sum);
}
}
// 处理剩余元素
for i in (chunks * 4)..a.len() {
result[i] = a[i] + b[i];
}
}
编译时启用 SIMD:
RUSTFLAGS='-C target-feature=+simd128' cargo build --target wasm32-unknown-unknown --release
Emscripten SIMD
#include <wasm_simd128.h>
void add_arrays_simd(const float* a, const float* b, float* result, int size) {
for (int i = 0; i < size; i += 4) {
wasm_v128_t va = wasm_v128_load(&a[i]);
wasm_v128_t vb = wasm_v128_load(&b[i]);
wasm_v128_t sum = wasm_f32x4_add(va, vb);
wasm_v128_store(&result[i], sum);
}
}
编译:
emcc main.c -msimd128 -O3 -o main.html
多线程优化
Web Workers
// 主线程
const memory = new WebAssembly.Memory({
initial: 10,
maximum: 100,
shared: true
});
const workers = [];
for (let i = 0; i < 4; i++) {
const worker = new Worker('worker.js');
worker.postMessage({ memory, workerId: i });
workers.push(worker);
}
// worker.js
self.onmessage = async function(e) {
const { memory, workerId } = e.data;
const { instance } = await WebAssembly.instantiateStreaming(
fetch('module.wasm'),
{ env: { memory } }
);
// 处理分配的任务
const start = workerId * 1000;
instance.exports.process(start, 1000);
};
原子操作
;; 原子加载
i32.atomic.load
i64.atomic.load
;; 原子存储
i32.atomic.store
i64.atomic.store
;; 原子加法
i32.atomic.rmw.add
i64.atomic.rmw.add
;; 原子比较交换
i32.atomic.rmw.cmpxchg
i64.atomic.rmw.cmpxchg
Rust 原子操作:
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new(0);
#[wasm_bindgen]
pub fn increment_counter() -> i32 {
COUNTER.fetch_add(1, Ordering::SeqCst)
}
加载优化
流式编译
// 推荐:流式编译
const { instance } = await WebAssembly.instantiateStreaming(
fetch('module.wasm'),
importObject
);
// 不推荐:等待完全下载
const response = await fetch('module.wasm');
const buffer = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(buffer, importObject);
预编译
// 预编译模块
const modulePromise = WebAssembly.compileStreaming(fetch('module.wasm'));
// 需要时实例化
async function getInstance() {
const module = await modulePromise;
return WebAssembly.instantiate(module, importObject);
}
代码分割
// 按需加载
async function loadModule(name) {
const { default: init } = await import(`./${name}.js`);
await init();
}
// 使用时加载
button.addEventListener('click', async () => {
await loadModule('heavy-module');
// 使用模块
});
压缩传输
# Nginx 配置
location ~ \.wasm$ {
add_header Content-Encoding br;
add_header Content-Type application/wasm;
}
性能分析
使用浏览器性能工具
Chrome DevTools:
- 打开 Performance 面板
- 点击 Record
- 执行操作
- 停止录制
- 分析结果
使用 console.time
console.time('wasm-operation');
instance.exports.heavyOperation();
console.timeEnd('wasm-operation');
Rust 性能分析
use web_sys::console;
#[wasm_bindgen]
pub fn profile_example() {
let start = js_sys::Date::now();
// 执行操作
heavy_computation();
let end = js_sys::Date::now();
console::log_1(&format!("Time: {}ms", end - start).into());
}
性能基准测试
使用 criterion
#[cfg(test)]
mod benches {
use super::*;
#[test]
fn bench_add() {
let start = std::time::Instant::now();
for _ in 0..1000000 {
add(1, 2);
}
let duration = start.elapsed();
println!("Time: {:?}", duration);
}
}
JavaScript 基准测试
function benchmark(name, fn, iterations = 10000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
console.log(`${name}: ${(end - start).toFixed(2)}ms`);
}
benchmark('WASM', () => instance.exports.process(data));
benchmark('JS', () => jsProcess(data));
最佳实践总结
编译优化
- 使用适当的优化级别(
-O2或-O3) - 启用链接时优化(LTO)
- 移除未使用的代码
体积优化
- 使用
opt-level = "s"或"z" - 使用
wee_alloc替代默认分配器 - 禁用不需要的功能
运行时优化
- 减少跨边界调用
- 批量处理数据
- 复用内存缓冲区
加载优化
- 使用流式编译
- 预编译模块
- 启用压缩传输
高级优化
- 使用 SIMD 并行处理
- 使用多线程处理大数据
- 使用原子操作保证线程安全
下一步
掌握性能优化后,你可以继续学习: