معالجة البيانات من جانب العميل باستخدام WebAssembly





WebAssembly (اختصارًا WASM) عبارة عن تقنية لتشغيل التعليمات البرمجية الثنائية المجمعة مسبقًا في مستعرض من جانب العميل. تم تقديمه لأول مرة في عام 2015 وهو مدعوم حاليًا من قبل معظم المتصفحات الحديثة.



إحدى حالات الاستخدام الشائعة هي المعالجة المسبقة من جانب العميل للبيانات قبل إرسال الملفات إلى الخادم. في هذه المقالة ، سوف نفهم كيف يتم ذلك.



قبل البداية



ووصف العمارة WebAssembly والخطوات العامة بمزيد من التفصيل هنا و هنا . سنستعرض الحقائق الأساسية فقط.



يبدأ العمل مع WebAssembly بالتجميع المسبق للقطع الأثرية المطلوبة لتشغيل التعليمات البرمجية المجمعة من جانب العميل. هناك نوعان منهم: ملف WASM الثنائي نفسه وطبقة JavaScript التي يمكنك من خلالها استدعاء الطرق المصدرة إليها.



مثال على أبسط كود C ++ للترجمة



#include <algorithm>

extern "C" {
int calculate_gcd(int a, int b) {
  while (a != 0 && b != 0) {
    a %= b;
    std::swap(a, b);
  }
  return a + b;
}
}


للتجميع ، يتم استخدام Emscripten ، والذي يحتوي ، بالإضافة إلى الواجهة الرئيسية لمثل هذه المجمعات ، على علامات إضافية يتم من خلالها ضبط تكوين الجهاز الظاهري وطرق التصدير. يبدو الإطلاق الأبسط كما يلي:



em++ main.cpp --std=c++17 -o gcd.html \
    -s EXPORTED_FUNCTIONS='["_calculate_gcd"]' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'


من خلال تحديد ملف * .html ككائن ، فإنه يخبر المترجم بإنشاء ترميز html بسيط مع وحدة تحكم js أيضًا. الآن ، إذا بدأنا الخادم على الملفات المستلمة ، فسنرى وحدة التحكم هذه مع إمكانية تشغيل _calculate_gcd:







معالجة البيانات



دعنا نحللها باستخدام مثال بسيط لضغط lz4 باستخدام مكتبة مكتوبة بلغة C ++. لاحظ أن العديد من اللغات المدعومة لا تنتهي عند هذا الحد.



على الرغم من البساطة وبعض الطبيعة التركيبية للمثال ، فإن هذا توضيح مفيد إلى حد ما لكيفية التعامل مع البيانات. وبالمثل ، يمكنك تنفيذ أي إجراءات عليها تكون فيها قوة العميل كافية: المعالجة المسبقة للصور قبل إرسالها إلى الخادم ، وضغط الصوت ، وإحصاء الإحصائيات المختلفة ، وغير ذلك الكثير.



يمكن العثور على الرمز بالكامل هنا.



جزء C ++



نستخدم تطبيقًا جاهزًا لـ lz4 . ثم سيبدو الملف الرئيسي مقتضبًا جدًا:



#include "lz4.h"

extern "C" {

uint32_t compress_data(uint32_t* data, uint32_t data_size, uint32_t* result) {
  uint32_t result_size = LZ4_compress(
        (const char *)(data), (char*)(result), data_size);
  return result_size;
}

uint32_t decompress_data(uint32_t* data, uint32_t data_size, uint32_t* result, uint32_t max_output_size) {
  uint32_t result_size = LZ4_uncompress_unknownOutputSize(
        (const char *)(data), (char*)(result), data_size, max_output_size);
  return result_size;
}

}


كما ترون، فإنه ببساطة يعلن الخارجية (باستخدام خارجي الكلمة ) وظائف التي تدعو داخليا أساليب المقابلة من المكتبة مع lz4.



وبصفة عامة، في حالتنا، ملف لا طائل منه: يمكنك استخدامها على الفور واجهة الأم من lz4.h . ومع ذلك ، في المشاريع الأكثر تعقيدًا (على سبيل المثال ، الجمع بين وظائف المكتبات المختلفة) ، من الملائم وجود نقطة دخول مشتركة تسرد جميع الوظائف المستخدمة.



بعد ذلك ، نقوم بتجميع الكود باستخدام مترجم Emscripten المذكور بالفعل :



em++ main.cpp lz4.c -o wasm_compressor.js \
    -s EXPORTED_FUNCTIONS='["_compress_data","_decompress_data"]' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
    -s WASM=1 -s ALLOW_MEMORY_GROWTH=1


حجم القطع الأثرية المستلمة ينذر بالخطر:



$ du -hs wasm_compressor.*
112K    wasm_compressor.js
108K    wasm_compressor.wasm


إذا فتحت طبقة ملف JS ، يمكنك رؤية شيء مثل ما يلي:







يحتوي على الكثير من الأشياء غير الضرورية: من التعليقات إلى وظائف الخدمة ، والتي لا يتم استخدام معظمها. يمكن تصحيح الموقف عن طريق إضافة علامة -O2 ، في مترجم Emscripten ، كما يتضمن تحسين كود js.



بعد ذلك ، يبدو كود js أجمل:







رمز العميل



تحتاج إلى استدعاء معالج جانب العميل بطريقة ما. بادئ ذي بدء ، قم بتحميل الملف الذي قدمه المستخدم من خلال FileReader، وسوف نقوم بتخزين البيانات الأولية بشكل بدائي Uint8Array:



var rawData = new Uint8Array(fileReader.result);


بعد ذلك ، تحتاج إلى نقل البيانات التي تم تنزيلها إلى الجهاز الظاهري. للقيام بذلك ، نخصص أولاً العدد المطلوب من البايت باستخدام التابع _malloc ، ثم انسخ مصفوفة JS هناك باستخدام التابع set. للراحة ، دعنا نفصل هذا المنطق في دالة arrayToWasmPtr (مجموعة):




function arrayToWasmPtr(array) {
  var ptr = Module._malloc(array.length);
  Module.HEAP8.set(array, ptr);
  return ptr;
}


بعد تحميل البيانات في ذاكرة الجهاز الظاهري ، تحتاج إلى استدعاء الوظيفة بطريقة ما من المعالجة. لكن كيف تجد هذه الوظيفة؟ ستساعدنا طريقة cwrap - تحدد الوسيطة الأولى فيها اسم الوظيفة المطلوبة ، والثانية - نوع الإرجاع ، والثالثة - قائمة مع وسيطات الإدخال.




compressDataFunction = Module.cwrap('compress_data', 'number', ['number', 'number', 'number']);


أخيرًا ، تحتاج إلى إرجاع البايتات النهائية من الجهاز الظاهري. للقيام بذلك ، نكتب وظيفة أخرى تنسخها في مصفوفة JS باستخدام هذه الطريقةsubarray



function wasmPtrToArray(ptr, length) {
  var array = new Int8Array(length);
  array.set(Module.HEAP8.subarray(ptr, ptr + length));
  return array;
}


البرنامج النصي الكامل لمعالجة الملفات الواردة هنا . ترميز HTML يحتوي على نموذج تحميل ملف وتحميل عناصر wasm هنا .



النتيجة



يمكنك اللعب مع النموذج الأولي هنا .



والنتيجة هي عمل نسخة احتياطية باستخدام WASM. من السلبيات - لا يسمح التطبيق الحالي للتكنولوجيا بتحرير الذاكرة المخصصة في الجهاز الظاهري. يؤدي هذا إلى حدوث تسرب ضمني عند تحميل عدد كبير من الملفات في جلسة واحدة ، ولكن يمكن إصلاحه عن طريق إعادة استخدام الذاكرة الحالية بدلاً من تخصيص ذاكرة جديدة.










All Articles