Design: 传递数据的最高效方式 JS/WASM 上下文

创建于 2018-09-12  ·  18评论  ·  资料来源: WebAssembly/design

你好,

假设我有一些 1024 f32值存储在一些Float32Array缓冲区中,等待由基于 WASM 的 DSP 代码处理:)

我对 WebAssembly 还是很陌生。 我知道您只能将输入的数值作为参数传递给导出的 WASM 函数。 这对我来说很有意义,这就是我决定使用内存传递数据的原因。 我也很好...

因此,为了传递我的 1024 个值,我将它们直接分配给.memory 。 喜欢:

const mem = exports.memory.buffer;
const F32 = new Float32Array(mem);
F32[0] = 31337.777;

这很有趣,但要分配我的所有值,我必须遍历所有值以将它们全部分配给内存。 嗯,不知何故,在性能方面感觉不对。 我本来希望有一个本地实现。 为我做那件事。 例如像键datamemoryDescriptor所述的参数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 之类的工具使其更易于使用。

所有18条评论

您必须决定是要在 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

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

Artur-A picture Artur-A  ·  3评论

nikhedonia picture nikhedonia  ·  7评论

bobOnGitHub picture bobOnGitHub  ·  6评论

konsoletyper picture konsoletyper  ·  6评论

badumt55 picture badumt55  ·  8评论