把概念换到性能优化时踩过的坑
把概念换到性能优化时踩过的坑我没按教科书顺序做。
这次做 WebAssembly 优化,从概念到实践,。
WebAssembly 基础
为什么需要 WebAssembly
// JavaScript 性能瓶颈
function calculatePrimes(max) {
const primes = [];
for (let i = 2; i <= max; i++) {
let isPrime = true;
for (let j = 2; j <= Math.sqrt(i); j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.push(i);
}
}
return primes;
}
// 测试性能
console.time('JS Primes');
const primes = calculatePrimes(100000);
console.timeEnd('JS Primes');
// JS Primes: 1234ms
// 问题:
// 1. 计算密集型任务慢
// 2. 内存占用高
// 3. 无法充分利用 CPU
WebAssembly 基本概念
WebAssembly (Wasm) 是一种二进制指令格式,可以在现代 Web 浏览器中运行。
优势:
- 接近原生性能
- 可预测的执行
- 体积小
- 多语言支持
第一个 WebAssembly 程序
编写 C 代码
// primes.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int* calculate_primes(int max, int* count) {
int* primes = malloc(max * sizeof(int));
*count = 0;
for (int i = 2; i <= max; i++) {
int is_prime = 1;
for (int j = 2; j <= sqrt(i); j++) {
if (i % j == 0) {
is_prime = 0;
break;
}
}
if (is_prime) {
primes[(*count)++] = i;
}
}
return primes;
}
编译为 WebAssembly
# 安装 Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
# 编译 C 代码为 WebAssembly
emcc primes.c -o primes.html \
-s EXPORTED_FUNCTIONS='["_calculate_primes", "_malloc", "_free"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
-s MODULARIZE=1 \
-s EXPORT_NAME="createPrimesModule"
在 JavaScript 中使用
let Module = null;
// 加载 WebAssembly 模块
async function loadWasm() {
try {
Module = await createPrimesModule();
console.log('WebAssembly module loaded');
} catch (error) {
console.error('Failed to load WebAssembly:', error);
}
}
// 使用 WebAssembly 函数
function calculatePrimesWasm(max) {
if (!Module) {
throw new Error('WebAssembly module not loaded');
}
const calculatePrimes = Module.cwrap('calculate_primes', 'number', ['number', 'number']);
const malloc = Module.cwrap('malloc', 'number', ['number']);
const free = Module.cwrap('free', 'void', ['number']);
// 分配内存
const countPtr = malloc(4);
// 调用 WebAssembly 函数
const primesPtr = calculatePrimes(max, countPtr);
// 读取结果
const count = Module.HEAP32[countPtr / 4];
const primes = [];
for (let i = 0; i < count; i++) {
primes.push(Module.HEAP32[(primesPtr / 4) + i]);
}
// 释放内存
free(countPtr);
return primes;
}
// 比较性能
async function comparePerformance() {
await loadWasm();
const max = 100000;
// JavaScript 版本
console.time('JS Primes');
const jsPrimes = calculatePrimes(max);
console.timeEnd('JS Primes');
// WebAssembly 版本
console.time('Wasm Primes');
const wasmPrimes = calculatePrimesWasm(max);
console.timeEnd('Wasm Primes');
console.log(`JS: ${jsPrimes.length} primes`);
console.log(`Wasm: ${wasmPrimes.length} primes`);
}
comparePerformance();
高级用法
与 JavaScript 互操作
// complex_calculations.c
#include <math.h>
double calculate_distance(double x1, double y1, double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
return sqrt(dx * dx + dy * dy);
}
void calculate_distances(double* points, double* results, int count) {
for (int i = 0; i < count; i++) {
double x1 = points[i * 4];
double y1 = points[i * 4 + 1];
double x2 = points[i * 4 + 2];
double y2 = points[i * 4 + 3];
results[i] = calculate_distance(x1, y1, x2, y2);
}
}
// 在 JavaScript 中使用
async function loadComplexCalculations() {
const Module = await createCalculationsModule();
const calculateDistances = Module.cwrap(
'calculate_distances',
'void',
['number', 'number', 'number']
);
const malloc = Module.cwrap('malloc', 'number', ['number']);
const free = Module.cwrap('free', 'void', ['number']);
// 准备数据
const points = [
0, 0, 3, 4,
1, 1, 4, 5,
2, 2, 5, 6
];
const pointsPtr = malloc(points.length * 8);
const resultsPtr = malloc(points.length / 4 * 8);
// 写入数据
for (let i = 0; i < points.length; i++) {
Module.HEAPF64[(pointsPtr / 8) + i] = points[i];
}
// 调用 WebAssembly 函数
calculateDistances(pointsPtr, resultsPtr, points.length / 4);
// 读取结果
const results = [];
for (let i = 0; i < points.length / 4; i++) {
results.push(Module.HEAPF64[(resultsPtr / 8) + i]);
}
// 释放内存
free(pointsPtr);
free(resultsPtr);
console.log('Distances:', results);
}
loadComplexCalculations();
Rust 和 WebAssembly
// 借用 cargo 工具
// cargo.toml
[package]
name = "wasm_module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n;
}
fibonacci(n - 1) + fibonacci(n - 2)
}
#[wasm_bindgen]
pub fn process_data(data: Vec<f64>) -> Vec<f64> {
data.iter().map(|x| x * 2.0).collect()
}
# 编译 Rust 为 WebAssembly
cargo install wasm-bindgen-cli
cargo build --target wasm32-unknown-unknown --release
wasm-bindgen target/wasm32-unknown-unknown/release/wasm_module.wasm --out-dir . --web
// 在 JavaScript 中使用
async function loadRustWasm() {
const module = await import('./wasm_module.js');
console.log('Fibonacci(10):', module.fibonacci(10));
console.log('Processed data:', module.process_data([1.0, 2.0, 3.0]));
}
loadRustWasm();
性能优化
内存管理
// 使用内存池减少内存分配
class WasmMemoryPool {
constructor(module, blockSize = 1024) {
this.module = module;
this.blockSize = blockSize;
this.blocks = [];
this.malloc = module.cwrap('malloc', 'number', ['number']);
this.free = module.cwrap('free', 'void', ['number']);
}
allocate() {
if (this.blocks.length > 0) {
return this.blocks.pop();
}
return this.malloc(this.blockSize);
}
deallocate(ptr) {
this.blocks.push(ptr);
}
cleanup() {
while (this.blocks.length > 0) {
this.free(this.blocks.pop());
}
}
}
// 使用内存池
const memoryPool = new WasmMemoryPool(Module);
function calculateWithMemoryPool(data) {
const ptr = memoryPool.allocate();
try {
// 使用 WebAssembly 函数处理数据
process_data(ptr, data.length);
return read_results(ptr, data.length);
} finally {
memoryPool.deallocate(ptr);
}
}
SIMD 优化
// SIMD 优化的版本
#include <emmintrin.h>
void simd_process(float* input, float* output, int count) {
// 确保数据对齐
int simd_count = count / 4;
for (int i = 0; i < simd_count; i++) {
// 加载 4 个 float
__m128 data = _mm_load_ps(&input[i * 4]);
// SIMD 操作
__m128 result = _mm_mul_ps(data, _mm_set1_ps(2.0f));
// 存储结果
_mm_store_ps(&output[i * 4], result);
}
// 处理剩余数据
for (int i = simd_count * 4; i < count; i++) {
output[i] = input[i] * 2.0f;
}
}
Worker 并行
// 使用 Web Worker 并行处理
class WasmWorker {
constructor(wasmModule) {
this.workers = [];
this.wasmModule = wasmModule;
this.workerCount = navigator.hardwareConcurrency || 4;
}
async initialize() {
for (let i = 0; i < this.workerCount; i++) {
const worker = new Worker('wasm-worker.js');
await new Promise((resolve) => {
worker.onmessage = (e) => {
if (e.data.type === 'ready') {
resolve();
}
};
worker.postMessage({ type: 'init', wasmModule: this.wasmModule });
});
this.workers.push(worker);
}
}
async processData(data) {
const chunkSize = Math.ceil(data.length / this.workerCount);
const promises = [];
for (let i = 0; i < this.workerCount; i++) {
const chunk = data.slice(i * chunkSize, (i + 1) * chunkSize);
promises.push(this.processChunk(this.workers[i], chunk));
}
const results = await Promise.all(promises);
return results.flat();
}
processChunk(worker, chunk) {
return new Promise((resolve) => {
worker.onmessage = (e) => {
resolve(e.data.result);
};
worker.postMessage({ type: 'process', data: chunk });
});
}
}
踩过的坑
坑一:内存泄漏
WebAssembly 内存没有正确释放。
解决:及时释放分配的内存。
// 正确的内存管理
function process_data_safely(data) {
const dataPtr = malloc(data.length * 4);
try {
// 写入数据
for (let i = 0; i < data.length; i++) {
Module.HEAP32[(dataPtr / 4) + i] = data[i];
}
// 处理数据
process_data_wasm(dataPtr, data.length);
// 读取结果
const result = [];
for (let i = 0; i < data.length; i++) {
result.push(Module.HEAP32[(dataPtr / 4) + i]);
}
return result;
} finally {
free(dataPtr); // 确保释放内存
}
}
坑二:数据传输慢
JavaScript 和 WebAssembly 之间数据传输慢。
解决:使用 SharedArrayBuffer 或优化数据结构。
// 使用 SharedArrayBuffer
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
// WebWorker 中使用
postMessage({ sharedBuffer }, [sharedBuffer]);
坑三:跨平台兼容性
不同浏览器对 WebAssembly 支持程度不同。
解决:提供降级方案。
async function loadWasmWithFallback() {
try {
const module = await createPrimesModule();
return module;
} catch (error) {
console.warn('WebAssembly not supported, falling back to JavaScript');
// JavaScript 降级方案
return {
calculate_primes: calculatePrimes
};
}
}
写在最后
WebAssembly 这东西,不只是技术,是性能革命。
解决了:
- 计算密集型任务性能
- 跨语言代码复用
- 近原生性能
带来了:
- 复杂度增加
- 调试困难
- 体积增加
使用之前先评估:
- 性能要求
- 开发成本
- 团队能力
- 浏览器支持
不是所有场景都需要 WebAssembly,有时候 JavaScript 就够用。
这次 WebAssembly 优化花了一个月,从概念到性能优化。优化完成后,计算密集型任务性能提升了 10 倍,用户体验提升明显。
版权声明: 本文首发于 指尖魔法屋-把概念换到性能优化时踩过的坑(https://blog.thinkmoon.cn/post/93-webassembly-concept-performance-optimization-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。