首页 > 解决方案 > 为 WebAssembly 模块重新分配 imports.env.memory 不会取代堆

问题描述

我正在为专有视频格式开发浏览器内播放器。我们的旧播放器是一个 Chrome NaCl 插件,而替换将在 JavaScript 和 WebAssembly 中实现。

视频解码将使用两个 webworker 在管道中完成,两个 webworker 都管理 WASM 模块。一名工作人员进行 zlib 解压缩,而另一名工作人员对像素进行解码。避免缓冲区复制对性能很重要。所以我提议的管道是这样的:

为了看看我能不能做到这一点,我设置了一个实验如下:

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 函数,那可能会导致浏览器失败或崩溃。

还有其他想法吗?

谢谢!

标签: multithreadingwebassembly

解决方案


选项 2:也许我可以为每个要解码的帧创建一个新的 WASM 模块实例。但是如何只加载和编译一次 WASM 代码并继续重用它呢?

模块是编译的,而不是实例。因此,每个新实例都在重用已编译的模块。


推荐阅读