Hola,
imaginemos que tengo unos 1024 f32
valores almacenados en algún búfer Float32Array
, esperando ser procesados por el código DSP basado en WASM :)
Todavía soy bastante nuevo en WebAssembly. Comprendí que solo puede pasar valores numéricos escritos como argumentos a funciones WASM exportadas. Eso tiene sentido para mí y es por eso que decidí pasar mis datos usando la memoria. Estoy bien con eso también ...
Entonces, para pasar mis 1024 valores, los asigno directamente al .memory
. Igual que:
const mem = exports.memory.buffer;
const F32 = new Float32Array(mem);
F32[0] = 31337.777;
Esto es divertido, pero para asignar todos mis valores, tengo que recorrer todos los valores para asignarlos todos a la memoria. Bueno, de alguna manera eso se siente mal en cuanto a rendimiento. Hubiera esperado un impl nativo. para hacer eso por mí. por ejemplo, una llave como data
en el memoryDescriptor
argumento del WebAssembly.Memory
constructor que permite inicializar la memoria con un ArrayBuffer
.
Entonces, bueno, entonces hago mi llamada a la función WASM y cuando el WASM impl. hizo su magia, está escribiendo los valores de resultado en la memoria. Esto está sucediendo dentro del bucle DSP, por lo que no hay una sobrecarga dentro de mi código WASM para hacer eso, por lo que puedo ver.
Pero ahora, después de volver al contexto JS, tengo que iterar sobre toda la memoria nuevamente solo para leer todos los valores y construir otra representación de datos basada en JS. Y de alguna manera esperaba un impl nativo. estar presente también para este propósito. Tal vez algo como memory.read(Float32Array)
para devolverme los datos del búfer como Float32Array
, abstrayendo el puntero y el laberinto de iteración.
¿Me estoy perdiendo de algo?
¿Existe una mejor manera de pasar grandes cantidades de datos desde / hacia WASM que acabo de pasar por alto?
Gracias de antemano y lo mejor,
Aron
Debe decidir si desea copiar datos entre JavaScript y WebAssembly, o si desea que WebAssembly sea el propietario de los datos.
Si desea copiar los datos, puede usar TypedArray.prototype.set () en lugar de escribir el bucle for
usted mismo:
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);
Si WebAssembly es el propietario de los datos, puede crear una vista sobre el búfer WebAssembly.Memory
y pasarla también a sus funciones de 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.
También puede usar herramientas como embind para que esto sea un poco más fácil de usar.
Vaya, gracias @binji, esto se ve increíble. Es exactamente lo que estaba buscando. También gracias por tomarse el tiempo para escribir el código de ejemplo. Esto también es muy útil. Lo intentaré esta noche y solicitaré algunas mejoras para el Loader impl. que está en uso para interactuar con WASM en https://github.com/AssemblyScript/assemblyscript :) (Es por eso que no encontré embind, que se ve increíble para usar con emscripten)
¿Ve una opción para ampliar los documentos en webassembly.org con respecto a esto?
Por ejemplo aquí:
https://webassembly.org/docs/web/
Estoy dispuesto a hacer esto si es posible.
Creo que también sería una buena idea explicarlo aquí:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory
@binji Fuera del tema, es posible que le guste https://github.com/torch2424/wasmBoy si aún no conoce el proyecto. Y este proyecto también podría beneficiarse de la solución que propuso, ya que utilizan bucles for para configurar la memoria ROM.
¿Ve una opción para ampliar los documentos en webassembly.org con respecto a esto?
Estuve de acuerdo en que deberíamos actualizar webassembly.org, con este cambio y mucho más también. Los documentos todavía son del repositorio de diseño, pero el repositorio de especificaciones es mucho más reciente y preciso.
Agregar algunos ejemplos a MDN probablemente sería más fácil y mejor, considerando que muchos más desarrolladores web usarán ese sitio que webassembly.org.
Fuera del tema, es posible que le guste https://github.com/torch2424/wasmBoy si aún no conoce el proyecto.
Sí, lo sé @ torch2424. :-) También tengo mi propio emulador de gameboy basado en wasm: https://github.com/binji/binjgb
Agregar algunos ejemplos a MDN probablemente sería más fácil y mejor, considerando que muchos más desarrolladores web usarán ese sitio que webassembly.org.
Muy bien, publicaré una actualización esta noche. Veamos si se publica.
Sí, lo sé @ torch2424. :-) También tengo mi propio emulador de gameboy basado en wasm: https://github.com/binji/binjgb
¡Jaja bien! Yo def. pruébalo :) Apuesto a que tu emulador no falla en Zelda - Link's Awakening cuando obtienes la espada (en la playa);))
Espero encontrar algo de tiempo para solucionar el bloqueo en wasmBoy también :)
¡Oye! ¡Rad que encontraste wasmboy! 😄 Sí, el emulador de @binji es definitivamente mucho más preciso, ¡jaja! Algo en lo que necesito trabajar con seguridad. Pero, irónicamente, uso el despertar de Link para probar a wasmboy todo el tiempo, ¡jaja! Me sorprende que se haya estrellado contra ti.
Abrió algunos errores basados en los comentarios:
https://github.com/torch2424/wasmBoy/issues/141 - Paso de memoria
https://github.com/torch2424/wasmBoy/issues/142 - Zelda Crash (con capturas de pantalla me funciona)
De todos modos, no quiero descarrilar la conversación / problema, pasaré a los problemas que abrí. ¡Pero gracias por el comentario! 😄
Si WebAssembly es el propietario de los datos, puede crear una vista sobre el búfer de WebAssembly.Memory y pasarla también a sus funciones de JavaScript:
Una advertencia importante: la vista en el búfer de memoria se invalidará si la instancia de WebAssembly aumenta su memoria. Evite almacenar referencias a la vista que persistan después de otras llamadas en la instancia de WebAssembly.
@binji ¿Puedes decirme qué pones en el lado de AssemblyScript cuando usas esto? Noté que no es una función estándar.
let myWasmArrayPtr = instance.exports.allocateF32Array(length);
Escribo lo siguiente y funciona, pero todavía quiero saber si es correcto.
export function allocateF32Array(length: usize): usize {
return memory.allocate(length * sizeof<f32>());
}
¿Necesito escribir otra función AS para liberarla con memory.free()
y llamarla después de transferir la matriz al lado JS?
@fmkang Entonces, lo que @binji explicó fue del lado JS (
Al usar Typed Arrays , puede escribir de manera eficiente en la memoria WASM desde el lado JS usando set () con Wasm Array Buffer.
Escribiendo en Wasm Memory desde dentro de Wasm / AS, todavía estoy usando el enfoque estándar para bucle, por ejemplo: https://github.com/torch2424/wasmBoy/blob/master/core/memory/dma.ts#L29
Sin embargo, tal vez @MaxGraey o @dcodeIO tal vez podrían ayudar con la forma de hacer esto de manera más eficiente desde dentro de AS o Wasm land. 😄 Aunque es mejor que cambiemos esto al repositorio de AS
@fmkang Simplemente
export function allocateF32Array(length: i32): Float32Array {
return new Float32Array(length);
}
en el lado AS y
let myArray = module.getArray(Float32Array, module.allocateF32Array(length));
en el lado JS, usando el cargador .
@ torch2424 Sí, el código de @binji está en el lado JS, pero let myWasmArrayPtr = instance.exports.allocateF32Array(length)
está llamando a una función llamada allocateF32Array
en el módulo Wasm (probablemente compilado por AssembleScript). Esta función no se menciona en su fragmento ni es parte de las funciones integradas de AS, por lo que creo que debería implementarla yo mismo. Estoy preguntando cómo obtener este puntero, que apunta a la matriz Wasm correspondiente.
@dcodeIO Gracias. Parece que esto simplifica el paso de matrices. Lo experimentaré cuidadosamente antes de publicar cualquier comentario nuevo aquí. Pero mi módulo falla cuando se carga después de reemplazar mi función con la suya. La consola dice
astest.html:1 Uncaught (in promise) TypeError: WebAssembly Instantiation: Import #0 module="env" error: module is not an object or function
Incluso se bloquea si escribo algo como let a = new Float32Array(10);
en cualquier parte de mi código AS.
@dcodeIO El módulo Wasm se bloquea cuando intentamos inicializar una matriz con expresiones (en lugar de literales, como let c: f64[] = [a[0] + b[0], a[1] + b[1]];
) o asignar valores a elementos de una matriz (como let a: f64[] = [0, 0, 0]; a[0] = 1;
o let array = new Array<i32>(); array.push(1);
).
Al principio, pensamos que esto se debe a que no pusimos memory
en env
, así que escribimos:
var importObject = {
env: { memory: new WebAssembly.Memory({initial:10}) },
imports: { imported_func: arg => console.log(arg) }
};
WebAssembly.instantiate(wasmBinary, importObject).then(...);
Pero el problema aún existía. Entonces nos dimos cuenta de que se debe a la falta de abort
. Nosotros escribimos:
env: {
abort(msg, file, line, column) {
console.error("abort called at main.ts:" + line + ":" + column);
}
}
o simplemente env: { abort: function(){} }
, y el problema resuelto. Sin embargo, no hay ningún mensaje de error y la ejecución del código realmente no "abortó". Aún no conocemos la causa real de este problema.
Somos realmente nuevos en WebAssembly. Escribo esta publicación solo para actualizar. Realmente no necesitas responder.
@fmkang Parece que no está utilizando el cargador de AssemblyScript para crear una instancia del módulo. Si bien puede implementar todo esto usted mismo, el cargador ya agrega funcionalidad básica en torno a las exportaciones de un módulo, como la función de cancelación. Si tiene más preguntas sobre AssemblyScript, no dude en consultar nuestro rastreador de problemas y estaremos encantados de ayudarle :)
Entonces, intenté usar esta técnica:
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);
Pero mi función processF32Array da como resultado RuntimeError: memory access out of bounds
. No estoy usando el cargador, FWIW. También probé:
let myArray = module.getArray(Float32Array, module.allocateF32Array(length));
Pero mi módulo no tiene un "getArray" y no estoy seguro de cómo se supone que debe obtenerlo.
Estaba buscando cómo copiar matrices desde / hacia WebAssembly y encontré este hilo. Para cualquier otra persona, pude copiar matrices usando AssemblyScript y la biblioteca de @ torch2424 as-bind
Aquí está mi simple prueba:
AssemblyScript
export function sum(arr: Float64Array): f64 {
let sum: f64 = 0;
for(let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
JavaScript (nodo)
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();
En mi máquina, obtuve el siguiente resultado:
Making array: 789.086ms
Reduce: 2452.922ms
JS For: 184.818ms
Wasm For: 2008.482ms
100000000 100000000 100000000
Parece que hay una buena sobrecarga al copiar la matriz en WebAssembly con este método, aunque puedo imaginar que valdrá la pena por una operación más cara que una suma.
Editar:
Eliminé la suma de AssemblyScript para aislar el tiempo de ejecución de solo la copia de la matriz:
export function sum(arr: Float64Array): f64 {
let sum: f64 = 0;
return sum;
}
Y volví a clasificar los puntos de referencia.
Sin suma de Wasm
Making array: 599.826ms
Reduce: 2810.395ms
JS For: 188.623ms
Wasm For: 762.481ms
100000000 100000000 0
Entonces, parece que la copia de datos en sí tomó 762.481ms para copiar más de 100 millones de f64
s.
@pwstegman Creé otro punto de referencia que usa datos de entrada aleatorios y evita que el motor js optimice su ciclo: https://webassembly.studio/?f=5ux4ymi345e
Y los resultados son diferentes para Chrome y 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
Hola.
Soy básicamente un recién nacido con respecto a la familiaridad con WASM, y me encontré con el mismo problema que la gente de aquí y en el n. ° 1162. (Estoy preguntando aquí contra # 1162 ya que este tiene actividad más reciente).
Después de una breve investigación, hice una demostración mientras intentaba resolverlo, y terminé con un método en el que expones las vistas en la memoria WASM como matrices escritas para evitar copiar datos dentro y fuera, aquí está la esencia desde el punto de vista de 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
¿Es este el camino a seguir? ¿Existe alguna solución mejor? Agradecería cualquier comentario / actualización sobre este tema.
ps, aunque un poco feo, funciona fantástico para mí en la práctica (hasta donde puedo entenderlo y apreciarlo)
pps
aquí está mi pregunta de stackoverflow (aún sin respuesta) con respecto a una preocupación similar:
https://stackoverflow.com/questions/65566923/is-there-a-more-efficient-way-to-return-arrays-from-c-to-javascript
Comentario más útil
Debe decidir si desea copiar datos entre JavaScript y WebAssembly, o si desea que WebAssembly sea el propietario de los datos.
Si desea copiar los datos, puede usar TypedArray.prototype.set () en lugar de escribir el bucle
for
usted mismo:Si WebAssembly es el propietario de los datos, puede crear una vista sobre el búfer
WebAssembly.Memory
y pasarla también a sus funciones de JavaScript:También puede usar herramientas como embind para que esto sea un poco más fácil de usar.