把概念换到性能优化时踩过的坑

把概念换到性能优化时踩过的坑我没按教科书顺序做。

这次做 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/) 转载或引用必须申明原指尖魔法屋来源及源地址!