Design: Cara paling berkinerja untuk melewatkan konteks data JS/WASM

Dibuat pada 12 Sep 2018  ·  18Komentar  ·  Sumber: WebAssembly/design

Hai,

mari kita bayangkan saya memiliki beberapa 1024 f32 nilai yang disimpan di beberapa Float32Array buffer, menunggu untuk diproses oleh kode DSP berbasis WASM :)

Saya masih cukup baru di WebAssembly. Saya mengerti bahwa Anda hanya dapat meneruskan nilai numerik yang diketik sebagai argumen ke fungsi WASM yang diekspor. Itu masuk akal bagi saya dan itulah mengapa saya memutuskan untuk meneruskan data saya menggunakan memori. Saya baik-baik saja dengan itu juga ...

Jadi, untuk meneruskan 1024 nilai saya, saya menetapkannya ke .memory secara langsung. Suka:

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

Ini menyenangkan, tetapi untuk menetapkan semua nilai saya, saya harus mengulang semua nilai untuk menetapkan semuanya ke memori. Yah, entah bagaimana itu terasa salah dari segi kinerja. Saya akan mengharapkan impl asli. untuk melakukan itu untukku. misalnya kunci seperti data dalam argumen memoryDescriptor dari WebAssembly.Memory konstruktor yang memungkinkan untuk menginisialisasi memori dengan ArrayBuffer .

Jadi, baiklah, lalu saya melakukan panggilan fungsi WASM saya dan ketika WASM impl. apakah itu ajaib, itu menulis nilai hasil kembali ke memori. Ini terjadi di dalam loop DSP, jadi tidak ada overhead di dalam kode WASM saya untuk melakukan itu - sejauh yang saya bisa lihat.

Tapi sekarang, setelah saya kembali dalam konteks JS, saya harus mengulangi seluruh memori lagi hanya untuk membaca semua nilai dan membangun representasi data berbasis JS lainnya. Dan entah bagaimana saya mengharapkan impl asli. hadir untuk tujuan ini juga. Mungkin sesuatu seperti memory.read(Float32Array) untuk mengembalikan saya data buffer sebagai Float32Array , mengabstraksi pointer dan labirin iterasi.

Apakah saya melewatkan sesuatu?
Apakah ada cara yang lebih baik untuk mengirimkan sejumlah besar data dari/ke WASM yang baru saja saya abaikan?

Terima kasih sebelumnya dan terbaik,
Aron

Komentar yang paling membantu

Anda harus memutuskan apakah Anda ingin menyalin data antara JavaScript dan WebAssembly, atau apakah Anda ingin WebAssembly memiliki data tersebut.

Jika Anda ingin menyalin data, Anda dapat menggunakan TypedArray.prototype.set() daripada menulis sendiri for loop:

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);

Jika WebAssembly memiliki data, Anda dapat membuat tampilan di atas buffer WebAssembly.Memory dan meneruskannya ke fungsi JavaScript Anda juga:

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.

Anda juga dapat menggunakan alat seperti embind untuk membuatnya sedikit lebih mudah digunakan.

Semua 18 komentar

Anda harus memutuskan apakah Anda ingin menyalin data antara JavaScript dan WebAssembly, atau apakah Anda ingin WebAssembly memiliki data tersebut.

Jika Anda ingin menyalin data, Anda dapat menggunakan TypedArray.prototype.set() daripada menulis sendiri for loop:

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);

Jika WebAssembly memiliki data, Anda dapat membuat tampilan di atas buffer WebAssembly.Memory dan meneruskannya ke fungsi JavaScript Anda juga:

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.

Anda juga dapat menggunakan alat seperti embind untuk membuatnya sedikit lebih mudah digunakan.

Wow, terima kasih @binji ini terlihat luar biasa. Itu persis apa yang saya cari. Juga terima kasih telah meluangkan waktu untuk menulis kode contoh. Ini juga sangat membantu. Saya akan mencobanya malam ini dan meminta beberapa perbaikan untuk impl Loader. yang digunakan untuk berinteraksi dengan WASM di https://github.com/AssemblyScript/assemblyscript :) (Itu sebabnya saya tidak menemukan embind, yang terlihat luar biasa untuk digunakan dengan emscripten)

Apakah Anda melihat opsi untuk memperpanjang dokumen di webassembly.org mengenai ini?

Misalnya di sini:

https://webassembly.org/docs/web/

Saya bersedia melakukan ini jika memungkinkan.

Saya pikir, itu juga ide yang baik untuk menjelaskannya di sini:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory

@binji Di luar topik, Anda mungkin menyukai https://github.com/torch2424/wasmBoy jika Anda belum mengetahui proyek tersebut. Dan proyek ini mungkin juga mendapat manfaat dari solusi yang Anda usulkan, karena mereka menggunakan for-loop untuk mengatur memori ROM.

Apakah Anda melihat opsi untuk memperpanjang dokumen di webassembly.org mengenai ini?

Setuju bahwa kita harus memperbarui webassembly.org, dengan perubahan ini dan banyak hal lainnya juga. Dokumen masih ada dari repo desain, tetapi repo spesifikasi jauh lebih baru dan akurat.

Menambahkan beberapa contoh ke MDN mungkin akan lebih mudah dan lebih baik, mengingat lebih banyak pengembang web yang akan menggunakan situs itu daripada webassembly.org.

Di luar topik, Anda mungkin menyukai https://github.com/torch2424/wasmBoy jika Anda belum mengetahui proyek tersebut.

Ya, saya tahu @torch2424. :-) Saya juga punya emulator gameboy berbasis wasm: https://github.com/binji/binjgb

Menambahkan beberapa contoh ke MDN mungkin akan lebih mudah dan lebih baik, mengingat lebih banyak pengembang web yang akan menggunakan situs itu daripada webassembly.org.

Baiklah, saya akan memposting pembaruan malam ini. Mari kita lihat apakah itu akan diterbitkan.

Ya, saya tahu @torch2424. :-) Saya juga punya emulator gameboy berbasis wasm: https://github.com/binji/binjgb

Haha, keren! saya akan def. mencobanya :) Saya yakin emulator Anda tidak macet di Zelda - Kebangkitan Tautan ketika Anda mendapatkan pedang (di pantai) ;))

Saya berharap menemukan waktu untuk memperbaiki kerusakan di wasmBoy juga :)

Hai! Rad bahwa Anda menemukan wasmboy! Ya, emulator @binji pasti jauh lebih akurat haha! Sesuatu yang harus saya kerjakan pasti. Tapi, Ironisnya, saya menggunakan kebangkitan Link untuk menguji wasmboy sepanjang waktu haha! Aku terkejut itu menabrakmu.

Membuka beberapa bug berdasarkan umpan balik:
https://github.com/torch2424/wasmBoy/issues/141 - Memory Passing
https://github.com/torch2424/wasmBoy/issues/142 - Zelda Crash (Dengan tangkapan layar berfungsi untuk saya)

Bagaimanapun, Tidak ingin menggagalkan percakapan/masalah, saya akan pindah ke masalah yang saya buka. Tapi terima kasih atas umpan baliknya! 😄

Jika WebAssembly memiliki data, Anda dapat membuat tampilan melalui buffer WebAssembly.Memory dan meneruskannya ke fungsi JavaScript Anda juga:

Satu peringatan penting: tampilan ke buffer Memori akan dibatalkan jika instance WebAssembly menambah memorinya. Hindari menyimpan referensi apa pun ke tampilan yang bertahan setelah panggilan lebih lanjut ke instance WebAssembly.

@binji Bisakah Anda memberi tahu saya apa yang Anda letakkan di sisi AssemblyScript saat Anda menggunakan ini? Saya perhatikan itu bukan fungsi standar.

let myWasmArrayPtr = instance.exports.allocateF32Array(length);

Saya menulis sebagai berikut dan berhasil, tetapi saya masih ingin tahu apakah itu dengan cara yang benar.

export function allocateF32Array(length: usize): usize {
    return memory.allocate(length * sizeof<f32>());
}

Apakah saya perlu menulis fungsi AS lain untuk membebaskannya dengan memory.free() , dan memanggilnya setelah saya mentransfer array ke sisi JS?

@fmkang Jadi apa yang dijelaskan @binji ada di sisi JS (mohon koreksi saya jika saya salah).

Menggunakan Typed Arrays , Anda dapat melakukan penulisan yang efisien ke dalam memori WASM dari sisi JS menggunakan set() dengan Wasm Array Buffer.

Menulis ke Memori Wasm dari dalam Wasm/AS, saya masih menggunakan pendekatan standar untuk loop, misalnya: https://github.com/torch2424/wasmBoy/blob/master/core/memory/dma.ts#L29

Padahal, mungkin @MaxGraey atau @dcodeIO mungkin bisa membantu dengan cara melakukan ini paling efisien dari dalam AS atau Wasm land? Meskipun kita mungkin lebih baik memindahkan ini ke repo AS

@fmkang Anda hanya

export function allocateF32Array(length: i32): Float32Array {
  return new Float32Array(length);
}

di sisi AS dan

let myArray = module.getArray(Float32Array, module.allocateF32Array(length));

di sisi JS, menggunakan loader .

@torch2424 Ya, kode @binji ada di sisi JS, tetapi let myWasmArrayPtr = instance.exports.allocateF32Array(length) memanggil fungsi bernama allocateF32Array dalam modul Wasm (kemungkinan dikompilasi oleh AssembleScript). Fungsi ini tidak disebutkan dalam cuplikannya atau bagian dari fungsi bawaan AS, jadi saya pikir itu harus diimplementasikan sendiri. Saya bertanya bagaimana cara mendapatkan pointer ini, yang menunjuk ke array Wasm yang sesuai.

@dcodeIO Terima kasih. Tampaknya ini menyederhanakan array yang lewat. Saya akan bereksperimen dengan hati-hati sebelum memposting komentar baru di sini. Tetapi modul saya macet saat dimuat setelah saya mengganti fungsi saya dengan milik Anda. Konsol mengatakan

astest.html:1 Uncaught (in promise) TypeError: WebAssembly Instantiation: Import #0 module="env" error: module is not an object or function

Bahkan crash jika saya menulis sesuatu seperti let a = new Float32Array(10); di mana saja dalam kode AS saya.

@dcodeIO Modul Wasm lumpuh saat kami mencoba menginisialisasi array dengan ekspresi (bukan literal, seperti let c: f64[] = [a[0] + b[0], a[1] + b[1]]; ) atau menetapkan nilai ke elemen array (seperti let a: f64[] = [0, 0, 0]; a[0] = 1; atau let array = new Array<i32>(); array.push(1); ).

Awalnya, kami mengira ini karena kami tidak memasukkan memory di env , jadi kami menulis:

var importObject = {
    env: { memory: new WebAssembly.Memory({initial:10}) },
    imports: { imported_func: arg => console.log(arg) }
};
WebAssembly.instantiate(wasmBinary, importObject).then(...);

Tapi masalahnya tetap ada. Kemudian kami kebetulan menemukannya karena kurangnya abort . Kami menulis:

env: {
    abort(msg, file, line, column) {
        console.error("abort called at main.ts:" + line + ":" + column);
    }
}

atau cukup env: { abort: function(){} } , dan masalahnya terpecahkan. Namun, tidak ada pesan kesalahan, dan eksekusi kode tidak benar-benar "dibatalkan". Kami masih belum mengetahui penyebab sebenarnya dari masalah ini.

Kami benar-benar baru di WebAssembly. Saya menulis posting ini hanya untuk memberikan pembaruan. Anda sebenarnya tidak perlu membalas.

@fmkang Tampaknya Anda tidak menggunakan pemuat AssemblyScript untuk membuat instance modul. Meskipun Anda dapat mengimplementasikan semua ini sendiri, loader sudah menambahkan fungsionalitas dasar seputar ekspor modul, seperti fungsi abort. Jika Anda memiliki pertanyaan lebih lanjut tentang AssemblyScript, jangan ragu untuk bertanya di pelacak masalah kami dan kami akan dengan senang hati membantu :)

Jadi, saya sudah mencoba menggunakan teknik ini:

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);

Tetapi fungsi processF32Array saya menghasilkan RuntimeError: memory access out of bounds . Saya tidak menggunakan pemuat, FWIW. Saya juga sudah mencoba:

let myArray = module.getArray(Float32Array, module.allocateF32Array(length));

Tetapi modul saya tidak memiliki "getArray," dan saya tidak yakin bagaimana seharusnya mendapatkannya.

Saya sedang mencari cara menyalin array ke/dari WebAssembly dan menemukan utas ini. Untuk orang lain, saya dapat menyalin array menggunakan AssemblyScript dan perpustakaan @ torch2424 as-bind

Inilah tes sederhana saya:

Naskah Perakitan

export function sum(arr: Float64Array): f64 {
  let sum: f64 = 0;
  for(let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
}

JavaScript (Node)

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();

Di mesin saya, saya mendapatkan output berikut:

Making array: 789.086ms
Reduce: 2452.922ms
JS For: 184.818ms
Wasm For: 2008.482ms
100000000 100000000 100000000

Sepertinya ada sedikit overhead saat menyalin array ke WebAssembly dengan metode ini, meskipun saya dapat membayangkan bahwa tradeoff akan sepadan untuk operasi yang lebih mahal daripada jumlah.

Sunting:

Saya menghapus penjumlahan dari AssemblyScript untuk mengisolasi waktu berjalan hanya salinan array:

export function sum(arr: Float64Array): f64 {
  let sum: f64 = 0;
  return sum;
}

Dan saya memutar ulang benchmark.

Tanpa penjumlahan Wasm

Making array: 599.826ms
Reduce: 2810.395ms
JS For: 188.623ms
Wasm For: 762.481ms
100000000 100000000 0

Jadi sepertinya salinan data itu sendiri membutuhkan 762,481 md untuk menyalin lebih dari 100 juta f64 dtk.

@pwstegman Saya membuat tolok ukur lain yang menggunakan data input acak dan mencegah mesin js mengoptimalkan loop Anda: https://webassembly.studio/?f=5ux4ymi345e

Dan hasilnya berbeda untuk 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

Halo.
Saya pada dasarnya baru lahir tentang keakraban dengan WASM, dan saya menemukan masalah yang sama dengan orang-orang di sini dan di #1162. (Saya bertanya di sini vs. #1162 karena yang ini memiliki aktivitas yang lebih baru.)
Setelah penelitian singkat saya membuat demo saat mencoba mencari tahu, dan berakhir dengan metode di mana Anda mengekspos pandangan ke memori WASM sebagai array yang diketik untuk menghindari menyalin data masuk dan keluar, inilah intinya dari sudut pandang 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

Apakah ini cara untuk pergi? Apakah ada solusi yang lebih baik? Akan sangat menghargai setiap komentar/penyegaran tentang topik ini.

ps meskipun agak jelek itu bekerja fantastis untuk saya dalam praktik (sejauh yang saya bisa mengerti dan menghargainya)

pps
inilah pertanyaan stackoverflow saya (belum terjawab) mengenai masalah serupa:

https://stackoverflow.com/questions/65566923/is-there-a-more-efficient-way-to-return-arrays-from-c-to-javascript

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

Artur-A picture Artur-A  ·  3Komentar

dpw picture dpw  ·  3Komentar

ghost picture ghost  ·  7Komentar

thysultan picture thysultan  ·  4Komentar

frehberg picture frehberg  ·  6Komentar