使用 d8 快速测试 WebAssembly 模块性能

- hikerpig
#Benchmark#V8#WebAssembly

d8 是 v8 的简易 CLI,它提供了一个极简的 JS 宿主环境,可用于 v8 的学习和调试,比起 nodejs,它更精简和专注。

除了 JS 以外,d8 也具有 WASM 的解释功能,可以方便地用于测试 WASM 模块的性能。如果你的 WASM 是要在浏览器里运行的,比起 wasmtime,v8(以及 spidermonkey 等主流浏览器 JS 引擎)显然是个更合适的命令行测试方案。

安装 v8

使用包管理器

brew install v8

使用 jsvu

jsvu 是 GoogleChromeLabs 出品的一个工具, 可以快速安装多个主流 JS 引擎(spidermonkey/v8/chakra/javascriptcore)。不过用它安装的 CLI 名字是 v8

使用例子

不同于 nodejs 的模块系统和库函数,d8 提供给脚本的全局函数很克制。简易的文档说明了几个,如直接使用 load() 加载和执行外部脚本至当前上下文,或是使用 read() 读取文件内容。

除了看文档外,还可以去查看 d8.cc 源码 获知 d8 script 支持的全部全局方法。

可使用 readbuffer() 方法获取 WASM 二进制内容。

假如用 rust 编译出了一个 rotate.wasm 模块 ,导出了一个简单方法 rotate_90,用于将图片旋转 90 度。

fn rotate_90(width: usize, height: usize, image_data_ptr: *mut u8)

简单的测试脚本 benchmark.js 如下:

const iterTimes = 10;
const imageDimensions = 4096;
const iterations = new Array(iterTimes);

const imageByteSize = imageDimensions * imageDimensions * 4;
const wasmPageSize = 64 * 1024;

const buffer = readbuffer("rotate.wasm");
const { instance } = await WebAssembly.instantiate(buffer);

const pagesAvailable = Math.floor(
instance.exports.memory.buffer.byteLength / wasmPageSize
);
const pagesNeeded = Math.floor((imageByteSize * 2 + 4) / wasmPageSize) + 1;
const additionalPagesNeeded = pagesNeeded - pagesAvailable;
if (additionalPagesNeeded > 0) {
instance.exports.memory.grow(additionalPagesNeeded);
}

const imageData = new ArrayBuffer(imageByteSize)

print(`==============================`);
for (let i = 0; i < iterTimes; i++) {
  const view = new Uint8ClampedArray(instance.exports.memory.buffer);
  const dataPtr = 8;
  view.set(imageData.data, dataPtr);

  const start = Date.now();
  instance.exports.rotate_90(imageDimensions, imageDimensions, dataPtr);
  iterations[i] = Date.now() - start;
}
const average = iterations.reduce((sum, c) => sum + c) / iterations.length;
const stddev = Math.sqrt(
  iterations
    .map(i => Math.pow(i - average, 2))
    .reduce((sum, c) => sum + c) / iterations.length
);
print(`n = ${iterations.length}`);
print(`Average: ${average}`);
print(`StdDev: ${stddev}`);

使用 d8 运行脚本:

d8 benchmark.js

结果打印出平均时间和方差:

==============================
n = 10
Average: 45.8
StdDev: 19.75246820020222

参考

Squoosh 项目的 codecs/rotate 模块。