你好,
假设我有一些 1024 f32
值存储在一些Float32Array
缓冲区中,等待由基于 WASM 的 DSP 代码处理:)
我对 WebAssembly 还是很陌生。 我知道您只能将输入的数值作为参数传递给导出的 WASM 函数。 这对我来说很有意义,这就是我决定使用内存传递数据的原因。 我也很好...
因此,为了传递我的 1024 个值,我将它们直接分配给.memory
。 喜欢:
const mem = exports.memory.buffer;
const F32 = new Float32Array(mem);
F32[0] = 31337.777;
这很有趣,但要分配我的所有值,我必须遍历所有值以将它们全部分配给内存。 嗯,不知何故,在性能方面感觉不对。 我本来希望有一个本地实现。 为我做那件事。 例如像键data
在memoryDescriptor
所述的参数WebAssembly.Memory
构造允许初始化有存储器ArrayBuffer
。
那么,好吧,然后我执行我的 WASM 函数调用,并且当 WASM impl. 这真的很神奇吗,它正在将结果值写回内存。 这是在 DSP 循环内部发生的,所以我的 WASM 代码内部没有开销来做到这一点 - 据我所知。
但是现在,在我回到 JS 上下文之后,我必须再次遍历整个内存,以读取所有值并构建另一个基于 JS 的数据表示。 不知何故,我期待一个原生的实现。 也为此目的而存在。 也许像memory.read(Float32Array)
东西将缓冲区数据作为Float32Array
返回给我,抽象指针和迭代迷宫。
我错过了什么吗?
有没有更好的方法可以将大量数据从我刚刚忽略的 WASM 传入/传到 WASM?
提前致谢,最好,
阿龙
您必须决定是要在 JavaScript 和 WebAssembly 之间复制数据,还是要让 WebAssembly 拥有数据。
如果你想复制数据,你可以使用TypedArray.prototype.set()而不是自己编写for
循环:
let instance = ...;
let myJSArray = new Float32Array(...);
let length = myJSArray.length;
let myWasmArrayPtr = instance.exports.allocateF32Array(length);
let myWasmArray = new Float32Array(instance.exports.memory.buffer, myWasmArrayPtr, length);
// Copy data in to be used by WebAssembly.
myWasmArray.set(myJSArray);
// Process the data in the array.
instance.exports.processF32Array(myWasmArrayPtr, length);
// Copy data out to JavaScript.
myJSArray.set(myWasmArray);
如果 WebAssembly 拥有数据,您可以在WebAssembly.Memory
缓冲区上创建一个视图,并将其传递给您的 JavaScript 函数:
let instance = ...;
let length = ...;
let myArrayPtr = instance.exports.allocateF32Array(length);
let myArray = new Float32Array(instance.exports.memory.buffer, myArrayPtr, length);
// Use myArray as a normal Float32Array in JavaScript, fill in data, etc.
...
// Process the data in the array.
instance.exports.processF32Array(myArrayPtr, length);
// No need to copy data back to JavaScript, just use myArray directly.
您还可以使用embind 之类的工具使其更易于使用。
哇,谢谢@binji,这看起来棒极了。 这正是我要找的。 也感谢您花时间编写示例代码。 这也非常有帮助。 我将在今晚尝试并为 Loader impl 请求一些改进。 用于与 https://github.com/AssemblyScript/assemblyscript 中的 WASM 接口 :) (这就是为什么我没有遇到 embind,它与 emscripten 一起使用看起来很棒)
您是否看到扩展 webassembly.org 上有关此文档的选项?
例如这里:
https://webassembly.org/docs/web/
如果可能的话,我愿意这样做。
我认为,在这里解释一下也是一个好主意:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory
@binji题外话,如果您还不了解该项目,您可能会喜欢https://github.com/torch2424/wasmBoy 。 这个项目也可能受益于您提出的解决方案,因为它们使用 for 循环来设置 ROM 内存。
您是否看到扩展 webassembly.org 上有关此文档的选项?
同意我们应该更新 webassembly.org,并进行此更改以及其他许多更改。 那里的文档仍然来自设计存储库,但规范存储库更新和准确得多。
考虑到比 webassembly.org 使用该站点的 Web 开发人员更多,将一些示例添加到 MDN 可能会更容易和更好。
题外话,如果您还不了解该项目,您可能会喜欢https://github.com/torch2424/wasmBoy 。
是的,我知道 @torch2424。 :-) 我也有自己的基于 wasm 的 gameboy 模拟器: https :
考虑到比 webassembly.org 使用该站点的 Web 开发人员更多,将一些示例添加到 MDN 可能会更容易和更好。
好的,今晚我会发布更新。 让我们看看它是否会发布。
是的,我知道 @torch2424。 :-) 我也有自己的基于 wasm 的 gameboy 模拟器: https :
哈哈,爽! 我会定义。 试试看:) 我敢打赌你的模拟器在塞尔达不会崩溃 - 当你拿到剑时(在海滩上)林克的觉醒 ;))
我也希望能找到一些时间来修复 wasmBoy 中的崩溃问题 :)
嘿! 你发现了wasmboy! 😄 是的, @binji的模拟器绝对是更准确的哈哈! 我肯定需要做的事情。 但是,讽刺的是,我一直在用Link的觉醒来测试wasmboy哈哈! 我很惊讶它撞到了你。
根据反馈打开了一些错误:
https://github.com/torch2424/wasmBoy/issues/141 - 内存传递
https://github.com/torch2424/wasmBoy/issues/142 - Zelda Crash(有截图对我有用)
无论如何,不想破坏对话/问题,我将转到我打开的问题。 但感谢您的反馈! 😄
如果 WebAssembly 拥有数据,您可以在 WebAssembly.Memory 缓冲区上创建一个视图并将其传递给您的 JavaScript 函数:
一个重要的警告:如果 WebAssembly 实例增加其内存,则内存缓冲区中的视图将无效。 避免存储对视图的任何引用,这些引用在进一步调用后仍然存在到 WebAssembly 实例中。
@binji你能告诉我你在使用它时在 AssemblyScript 端放了什么吗? 我注意到它不是标准功能。
let myWasmArrayPtr = instance.exports.allocateF32Array(length);
我写如下并且它有效,但我仍然想知道它是否以正确的方式。
export function allocateF32Array(length: usize): usize {
return memory.allocate(length * sizeof<f32>());
}
我是否需要编写另一个 AS 函数来使用memory.free()
释放它,并在将数组传输到 JS 端后调用它?
@fmkang所以@binji解释的是JS 方面(如果我错了,请纠正我)。
使用Typed Arrays ,您可以使用set()和 Wasm Array Buffer 从 JS 端高效地写入 WASM 内存。
从 Wasm/AS 内部写入 Wasm Memory,我仍然使用标准 for 循环方法,例如: https :
不过,也许@MaxGraey或@dcodeIO可能有助于从 AS 或 Wasm 土地内部最有效地做到这一点? 😄 虽然我们最好把它移到 AS 仓库
@fmkang你只是
export function allocateF32Array(length: i32): Float32Array {
return new Float32Array(length);
}
在 AS 端和
let myArray = module.getArray(Float32Array, module.allocateF32Array(length));
在 JS 方面,使用loader 。
@ torch2424是,@binji的代码是关于JS的一面,但let myWasmArrayPtr = instance.exports.allocateF32Array(length)
被调用名为函数allocateF32Array
的WASM模块(可能是由AssembleScript编译)英寸这个函数在他的snippet中没有提到,也没有作为AS内置函数的一部分,所以我觉得应该自己实现。 我在问如何获得这个指针,它指向相应的 Wasm 数组。
@dcodeIO谢谢。 这似乎简化了传递数组。 在此处发表任何新评论之前,我会仔细试验它。 但是在我用你的函数替换我的函数后,我的模块在加载时崩溃了。 控制台说
astest.html:1 Uncaught (in promise) TypeError: WebAssembly Instantiation: Import #0 module="env" error: module is not an object or function
如果我在 AS 代码的任何地方写类似let a = new Float32Array(10);
东西,它甚至会崩溃。
@dcodeIO当我们尝试使用表达式(而不是文字,例如let c: f64[] = [a[0] + b[0], a[1] + b[1]];
)初始化数组或为数组元素赋值(例如let a: f64[] = [0, 0, 0]; a[0] = 1;
或let array = new Array<i32>(); array.push(1);
)时,Wasm 模块崩溃
一开始,我们认为这是因为我们没有把memory
放在env
,所以我们写了:
var importObject = {
env: { memory: new WebAssembly.Memory({initial:10}) },
imports: { imported_func: arg => console.log(arg) }
};
WebAssembly.instantiate(wasmBinary, importObject).then(...);
但问题仍然存在。 然后我们碰巧发现是因为缺少abort
。 我们写:
env: {
abort(msg, file, line, column) {
console.error("abort called at main.ts:" + line + ":" + column);
}
}
或者干脆env: { abort: function(){} }
,问题就解决了。 但是,没有错误信息,代码的执行并没有真正“中止”。 我们仍然不知道这个问题的实际原因。
我们对 WebAssembly 真的很陌生。 我写这篇文章只是为了提供更新。 你真的不需要回复。
@fmkang您似乎没有使用AssemblyScript 加载器来实例化模块。 虽然您可以自己实现所有这些,但加载程序已经围绕模块的导出添加了基本功能,例如 abort 函数。 如果您对 AssemblyScript 有任何疑问,请随时在我们的问题跟踪器上提问,
所以,我尝试使用这种技术:
let myArrayPtr = instance.exports.allocateF32Array(length);
let myArray = new Float32Array(instance.exports.memory.buffer, myArrayPtr, length);
// Use myArray as a normal Float32Array in JavaScript, fill in data, etc.
...
// Process the data in the array.
instance.exports.processF32Array(myArrayPtr, length);
但是我的 processF32Array 函数导致RuntimeError: memory access out of bounds
。 我没有使用装载机,FWIW。 我也试过:
let myArray = module.getArray(Float32Array, module.allocateF32Array(length));
但是我的模块没有“getArray”,我不确定它应该如何获取它。
我一直在寻找如何将数组复制到/从 WebAssembly 复制并遇到了这个线程。 对于其他人,我可以使用AssemblyScript和@torch2424的库as-bind复制数组
这是我的简单测试:
组装脚本
export function sum(arr: Float64Array): f64 {
let sum: f64 = 0;
for(let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
JavaScript(节点)
const { AsBind } = require("as-bind");
const fs = require("fs");
const wasm = fs.readFileSync(__dirname + "/build/optimized.wasm");
const asyncTask = async () => {
const asb = await AsBind.instantiate(wasm);
// Make a large array
console.time('Making array');
let arr = new Float64Array(1e8).fill(1);
console.timeEnd('Making array');
// Find the sum using reduce
console.time('Reduce');
let sum1 = arr.reduce((acc, val) => acc + val, 0);
console.timeEnd('Reduce');
// Find the sum with for loops
console.time('JS For');
let sum2 = 0;
for(let i = 0; i < arr.length; i++) sum2 += arr[i];
console.timeEnd('JS For');
// Find the sum with WebAssembly
console.time('Wasm For');
let sum3 = asb.exports.sum(arr);
console.timeEnd('Wasm For');
console.log(sum1, sum2, sum3);
};
asyncTask();
在我的机器上,我得到以下输出:
Making array: 789.086ms
Reduce: 2452.922ms
JS For: 184.818ms
Wasm For: 2008.482ms
100000000 100000000 100000000
使用这种方法将数组复制到 WebAssembly 时,看起来有很大的开销,尽管我可以想象,对于比总和更昂贵的操作,权衡是值得的。
编辑:
我从 AssemblyScript 中删除了求和,以隔离数组副本的运行时间:
export function sum(arr: Float64Array): f64 {
let sum: f64 = 0;
return sum;
}
我重新运行了基准测试。
没有 Wasm 求和
Making array: 599.826ms
Reduce: 2810.395ms
JS For: 188.623ms
Wasm For: 762.481ms
100000000 100000000 0
所以看起来数据复制本身花了 762.481 毫秒来复制超过 1 亿f64
s。
@pwstegman我创建了另一个基准,它使用随机输入数据并防止 js 引擎优化你的循环: https : //webassembly.studio/?f=5ux4ymi345e
Chrome 和 FF 的结果不同:
Chrome 81.0.4044.129
js sum: 129.968017578125ms
sum result = 49996811.62100115
js reduce: 1436.532958984375ms
js reduce result = 49996811.62100115
wasm sum: 153.000244140625ms
wasm sum result = 49996811.62100115
wasm reduce: 125.009033203125ms
wasm reduce result = 49996811.62100115
wasm empty: 0.002685546875ms
你好。
在熟悉 WASM 方面,我基本上是一个新生儿,我偶然发现了与这里和 #1162 中的人们相同的问题。 (我在这里问与 #1162 的对比,因为这个有更多最近的活动。)
经过简短的研究,我在试图弄清楚时做了一个演示,最后得到了一种方法,将视图作为类型化数组公开到 WASM 内存中以避免将数据复制进出,这是从 JS 的角度来看它的要点:
// need to know the size in advance, that's OK since the impl makes some init
const fft = new Module.KissFftReal(/*size=*/N);depending on the size anyway
// get view into WASM memory as Float64Array (of size=N in this case)
const input = fft.getInputTimeDataBuffer();
// fill 'input' buffer
// perform transformation, view into WASM memory is returned here (of size=(N + 2) in this case, +2 for Nyquist bin)
const output = fft.transform();
// use transformation result returned in 'output' buffer
这是要走的路吗? 有没有更好的解决办法? 感谢您对此主题的任何评论/复习。
ps虽然有点丑,但它在实践中对我来说效果很好(据我了解和欣赏)
pps
这是我关于类似问题的(尚未回答的)stackoverflow 问题:
https://stackoverflow.com/questions/65566923/is-there-a-more-efficient-way-to-return-arrays-from-c-to-javascript
最有用的评论
您必须决定是要在 JavaScript 和 WebAssembly 之间复制数据,还是要让 WebAssembly 拥有数据。
如果你想复制数据,你可以使用TypedArray.prototype.set()而不是自己编写
for
循环:如果 WebAssembly 拥有数据,您可以在
WebAssembly.Memory
缓冲区上创建一个视图,并将其传递给您的 JavaScript 函数:您还可以使用embind 之类的工具使其更易于使用。