Design: الطريقة الأكثر أداءً لتمرير سياق البيانات JS / WASM

تم إنشاؤها على ١٢ سبتمبر ٢٠١٨  ·  18تعليقات  ·  مصدر: WebAssembly/design

أهلا،

دعنا نتخيل أن لدي بعض قيم 1024 f32 مخزنة في بعض المخزن المؤقت Float32Array ، في انتظار المعالجة بواسطة رمز DSP المستند إلى WASM :)

ما زلت جديدًا على 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. فعل السحر ، إنه يعيد كتابة قيم النتيجة إلى الذاكرة. يحدث هذا داخل حلقة DSP ، لذلك لا يوجد عبء داخل رمز WASM الخاص بي للقيام بذلك - بقدر ما أستطيع رؤيته.

ولكن الآن ، بعد أن عدت إلى سياق JS ، لا بد لي من تكرار الذاكرة بأكملها مرة أخرى فقط لقراءة جميع القيم وإنشاء تمثيل بيانات JS آخر. وبطريقة ما كنت أتوقع وجود ضمني محلي. ليكون حاضرًا لهذا الغرض أيضًا. ربما شيء مثل memory.read(Float32Array) يعيد لي بيانات المخزن المؤقت كـ Float32Array ، يجرد المؤشر ومتاهة التكرار.

هل فاتني شيء؟
هل هناك طريقة أفضل لتمرير كميات كبيرة من البيانات من / إلى 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 ، يبدو هذا رائعًا. هذا بالضبط ما كنت أبحث عنه. نشكرك أيضًا على الوقت الذي قضيته في كتابة رمز المثال. هذا مفيد جدا كذلك سأحاول هذا المساء وسحب بعض التحسينات على أداة التحميل. وهو قيد الاستخدام للتفاعل مع WASM في https://github.com/AssemblyScript/assemblyscript :) (لهذا السبب لم أجد 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 إذا لم تكن على علم بالفعل بالمشروع. وقد يستفيد هذا المشروع أيضًا من الحل الذي اقترحته ، حيث يستخدمون حلقات التكرار لتعيين ذاكرة ROM.

هل ترى خيارًا لتوسيع المستندات على webassembly.org بخصوص هذا؟

متفقًا على أنه يجب علينا تحديث webassembly.org ، مع هذا التغيير والكثير غير ذلك أيضًا. لا تزال المستندات موجودة من ريبو التصميم ، لكن ريبو المواصفات أكثر حداثة ودقة.

من المحتمل أن تكون إضافة بعض الأمثلة إلى MDN أسهل وأفضل ، مع الأخذ في الاعتبار أن العديد من مطوري الويب سيستخدمون هذا الموقع أكثر من webassembly.org.

خارج الموضوع ، قد ترغب في https://github.com/torch2424/wasmBoy إذا لم تكن على علم بالفعل بالمشروع.

نعم ، أعرف @ torch2424. :-) لدي محاكي gameboy الخاص بي القائم على wasm أيضًا: https://github.com/binji/binjgb

من المحتمل أن تكون إضافة بعض الأمثلة إلى MDN أسهل وأفضل ، مع الأخذ في الاعتبار أن العديد من مطوري الويب سيستخدمون هذا الموقع أكثر من webassembly.org.

حسنًا ، سأقوم بنشر تحديث الليلة. دعونا نرى ما إذا تم نشره.

نعم ، أعرف @ torch2424. :-) لدي محاكي gameboy الخاص بي القائم على wasm أيضًا: https://github.com/binji/binjgb

هاها ، رائع! سأفعل. جربه :) أراهن أن المحاكي الخاص بك لا يتعطل في Zelda - Link's Awakening عندما تحصل على السيف (على الشاطئ) ؛))

آمل أن أجد بعض الوقت لإصلاح التعطل في wasmBoy أيضًا :)

مهلا! راد أنك وجدت فتى wasmboy! 😄 نعم،binji الصورة المحاكي هو بالتأكيد wayyy هاها أكثر دقة! شيء أحتاج إلى العمل عليه بالتأكيد. ولكن ، من المفارقات ، أنني أستخدم إيقاظ الرابط لاختبار 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 (يرجى تصحيح ما إذا كنت مخطئًا).

باستخدام المصفوفات المكتوبة ، يمكنك القيام بالكتابة الفعالة في ذاكرة WASM من جانب JS باستخدام set () مع Wasm Array Buffer.

الكتابة إلى Wasm Memory من داخل Wasm / AS ، ما زلت أستخدم نهج الحلقة القياسية ، على سبيل المثال: https://github.com/torch2424/wasmBoy/blob/master/core/memory/dma.ts#L29

على الرغم من ذلك ، ربما يمكن أن يساعد MaxGraey أو dcodeIO في كيفية القيام بذلك بكفاءة أكبر من داخل AS أو أرض Wasm؟ 😄 على الرغم من أننا قد يكون من الأفضل نقل هذا إلى AS repo

fmkang كنت ببساطة

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

على الجانب AS و

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

على جانب JS ، باستخدام اللودر .

@ torch2424 نعم ، رمز binji موجود على جانب JS ، لكن let myWasmArrayPtr = instance.exports.allocateF32Array(length) يستدعي دالة تسمى allocateF32Array في وحدة Wasm (من المحتمل أن تكون قد جمعتها AssembleScript). لم يتم ذكر هذه الوظيفة في المقتطف الخاص به ولا جزء من الوظائف المضمنة في AS ، لذلك أعتقد أنه يجب تنفيذها بنفسي. أسأل كيف أحصل على هذا المؤشر ، الذي يشير إلى مصفوفة Wasm المقابلة.

dcodeIO شكرا. يبدو أن هذا يبسط المصفوفات المارة. سأجربها بعناية قبل نشر أي تعليقات جديدة هنا. لكن الوحدة النمطية الخاصة بي تتعطل عند تحميلها بعد أن استبدل وظيفتي بوظيفتك. تقول وحدة التحكم

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

حتى أنه يتعطل إذا كتبت شيئًا مثل let a = new Float32Array(10); في أي مكان في رمز AS الخاص بي.

dcodeIO تتعطل وحدة Wasm عندما كنا نحاول تهيئة مصفوفة باستخدام تعبيرات (بدلاً من القيم الحرفية ، مثل 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); ).

في البداية ، اعتقدنا أن هذا يرجع إلى أننا لم نضع 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 لإنشاء مثيل الوحدة. بينما يمكنك تنفيذ كل هذا بنفسك ، يضيف المُحمل بالفعل وظائف أساسية حول صادرات الوحدة ، مثل وظيفة الإحباط. إذا كان لديك أي أسئلة أخرى بخصوص 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 الصورة مكتبة كما هو وربط

هذا هو اختباري البسيط:

أسيمبلي سكريبت

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

جافا سكريبت (عقدة)

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 مللي ثانية لنسخ أكثر من 100 مليون f64 .

pwstegman لقد أنشأت معيارًا آخر يستخدم بيانات الإدخال العشوائي ويمنع محرك js من تحسين الحلقة الخاصة بك: https://webassembly.studio/؟

والنتائج تختلف بالنسبة لمتصفح 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

هل هذا هو الطريق للذهاب؟ هل هناك أي حل أفضل؟ نقدر أي تعليق / تجديد معلومات حول هذا الموضوع.

ملاحظة: على الرغم من أنه قبيح نوعًا ما ، إلا أنه يعمل بشكل رائع بالنسبة لي في الممارسة (بقدر ما يمكنني فهمه وتقديره)

ص
هذا سؤالي (الذي لم تتم الإجابة عليه) حول stackoverflow فيما يتعلق بمخاوف مماثلة:

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

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

JimmyVV picture JimmyVV  ·  4تعليقات

badumt55 picture badumt55  ·  8تعليقات

ghost picture ghost  ·  7تعليقات

chicoxyzzy picture chicoxyzzy  ·  5تعليقات

jfbastien picture jfbastien  ·  6تعليقات