multithreading - 为 WebAssembly 模块重新分配 imports.env.memory 不会取代堆
问题描述
我正在为专有视频格式开发浏览器内播放器。我们的旧播放器是一个 Chrome NaCl 插件,而替换将在 JavaScript 和 WebAssembly 中实现。
视频解码将使用两个 webworker 在管道中完成,两个 webworker 都管理 WASM 模块。一名工作人员进行 zlib 解压缩,而另一名工作人员对像素进行解码。避免缓冲区复制对性能很重要。所以我提议的管道是这样的:
- 在主 JS 线程中,分配一个新的 WebAssembly.Memory 对象并用来自视频源的数据填充它。
- 将所有权移交给解压工作人员。
- 用新内存替换其 WASM 模块的堆。
- 调用解压函数。
- 从此模块中分离堆并将所有权转移给解码工作人员。
- 用新内存替换此 WASM 模块的堆。
- 调用解码函数。
- 从此模块中分离堆并将所有权转移回 WebGL 显示的主线程。
为了看看我能不能做到这一点,我设置了一个实验如下:
WASM 模块的 C 代码:
void decode(unsigned char *in, unsigned char *out)
{
for (int i=0; i<10; i++) {
*out++ = *in++ + 100;
}
}
我这样编译它:
emcc test_wasm.c -O3 -s WASM=1 -s SIDE_MODULE=1 -s "EXPORTED_FUNCTIONS=['_decode']" -o test_wasm.wasm
这是加载它的 JS 代码:
导出类 LoadWasm { wasmOnLoad(obj) { this.instance = obj.instance; console.log("加载的 WASM"); 控制台.log(obj.instance);
// Fill buffer with some numbers
for (var i=0; i<10; i++) {
this.heap[i] = i;
}
// Have WASM code copy the data, adding 100 to each number
obj.instance.exports._decode(0, 128);
// Print output
for (var i=0; i<10; i++) {
console.log(i + ": " + this.heap[i] + " => " + this.heap[i+128]);
}
// Allocate a new heap for the WASM module
this.memory = new WebAssembly.Memory({
initial: 256
});
this.heap = new Uint8Array(this.memory.buffer);
// Replace the WASM module's heap
this.imports.env.memory = this.memory;
// Fill the heap with new data
for (var i=0; i<10; i++) {
this.heap[i] = i+10;
}
// Run the compute again
obj.instance.exports._decode(0, 128);
// Print out the results
for (var i=0; i<10; i++) {
console.log(i + ": " + this.heap[i] + " => " + this.heap[i+128]);
}
}
constructor() {
this.memory = new WebAssembly.Memory({
initial: 256
});
this.heap = new Uint8Array(this.memory.buffer);
this.imports = {
env: {
__memory_base: 0,
memory: this.memory,
abort: function(err) {
throw new Error('abort ' + err);
},
}
};
}
start() {
console.log("startWasm");
WebAssembly.instantiateStreaming(fetch('test_wasm.wasm'), this.imports)
.then(this.wasmOnLoad.bind(this));
}
}
它是从我的主 HTML 页面调用的,如下所示:
import { LoadWasm } from "./test_wasm.js";
var l = new LoadWasm();
l.start();
不幸的是,这不起作用。第一次调用_decode
工作正常。如果我不替换堆,它会再次正常工作。但是如果我确实替换了堆,我只会得到零。WASM 模块未使用新堆。
因为在 webworkers 和主 JS 线程之间还没有共享内存,所以在 webworkers 之间传输数据零拷贝的唯一方法是交出对象的所有权。结果,我不能做显而易见的事情并传递和填充现有堆的子数组。我需要能够在处理前一个堆时填充一个新堆,以获得所需的并行度。
为了解决这个问题,我可以想到几个选项:
选项 1:有没有办法替换已经实例化的 WASM 模块的堆,而我只是做错了?
选项 2:也许我可以为每个要解码的帧创建一个新的 WASM 模块实例。但是如何只加载和编译一次 WASM 代码并继续重用它呢?不过,我认为它仍然必须经过一个链接过程。有多少开销来做到这一点?这值得么?
选项 3:我了解 WASM 已经支持线程。我可以请求在主 JS 线程中进行解码,并且在 WASM 代码中,它将计算移交给工作线程。问题是找出计算何时完成。有没有办法从 WASM 中的非主线程执行 postMessage?似乎如果我尝试从非主 WASM 线程调用 JS 函数,那可能会导致浏览器失败或崩溃。
还有其他想法吗?
谢谢!
解决方案
选项 2:也许我可以为每个要解码的帧创建一个新的 WASM 模块实例。但是如何只加载和编译一次 WASM 代码并继续重用它呢?
模块是编译的,而不是实例。因此,每个新实例都在重用已编译的模块。
推荐阅读
- php - Wordpress 如何轻松将图像 url 更改为图像服务器路径
- javascript - Jquery 函数不适用于标题。当我使用 .load() 方法包含标题时
- angular - 使用材料 12 的 Angular 12 上的打字稿错误
- javascript - 在 Woocommerce 订单列表中对批量操作执行多项操作
- python - 在派生类的重写方法中键入可选的命名参数
- javascript - Protractor JS 中大型任务的最佳设计模式
- python - cx_oracle 的 executemany 与 batcherros=True 没有按预期工作
- asp.net - Asp.net Telerik 脚本问题
- scala - 旋转流式数据帧 pyspark
- javascript - 移动列的逻辑