Daten auf der Clientseite mit WebAssembly verarbeiten





WebAssembly (abgekürzt WASM) ist eine Technologie zum Ausführen von vorkompiliertem Binärcode in einem Browser auf der Clientseite. Es wurde erstmals im Jahr 2015 eingeführt und wird derzeit von den meisten modernen Browsern unterstützt.



Ein häufiger Anwendungsfall ist die clientseitige Vorverarbeitung von Daten vor dem Senden von Dateien an den Server. In diesem Artikel werden wir verstehen, wie dies gemacht wird.



Vor dem Anfang



Die WebAssembly-Architektur und allgemeine Schritte werden hier und hier ausführlicher beschrieben . Wir werden nur die grundlegenden Fakten durchgehen.



Die Arbeit mit WebAssembly beginnt mit der Vormontage der Artefakte, die zum Ausführen des kompilierten Codes auf der Clientseite erforderlich sind. Es gibt zwei davon: die eigentliche binäre WASM-Datei selbst und eine JavaScript-Ebene, über die Sie die exportierten Methoden aufrufen können.



Ein Beispiel für den einfachsten C ++ - Code zum Kompilieren



#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;
}
}


Für die Assembly wird Emscripten verwendet , das zusätzlich zur Hauptschnittstelle solcher Compiler zusätzliche Flags enthält, über die die Konfiguration der virtuellen Maschine und die exportierten Methoden festgelegt werden. Der einfachste Start sieht folgendermaßen aus:



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


Durch Angabe einer * .html- Datei als Objekt wird der Compiler angewiesen, ein einfaches HTML-Markup auch mit einer js-Konsole zu erstellen. Wenn wir nun den Server mit den empfangenen Dateien starten, wird diese Konsole mit der Möglichkeit angezeigt , _calculate_gcd zu starten :







Datenverarbeitung



Lassen Sie uns dies anhand eines einfachen Beispiels für die lz4-Komprimierung mithilfe einer in C ++ geschriebenen Bibliothek analysieren. Beachten Sie, dass die vielen unterstützten Sprachen dort nicht enden.



Trotz der Einfachheit und der synthetischen Natur des Beispiels ist dies ein ziemlich nützliches Beispiel für die Arbeit mit Daten. In ähnlicher Weise können Sie alle Aktionen ausführen, für die die Leistung des Clients ausreicht: Bildvorverarbeitung vor dem Senden an den Server, Audiokomprimierung, Zählen verschiedener Statistiken und vieles mehr.



Den gesamten Code finden Sie hier.



C ++ Teil



Wir verwenden eine vorgefertigte Implementierung von lz4 . Dann sieht die Hauptdatei sehr lakonisch aus:



#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;
}

}


Wie Sie sehen können, deklariert es einfach externe Funktionen (mit dem Schlüsselwort extern ), die intern die entsprechenden Methoden aus der Bibliothek mit lz4 aufrufen.



Im Allgemeinen ist die Datei in unserem Fall nutzlos: Sie können sofort die native Schnittstelle von lz4.h verwenden . In komplexeren Projekten (z. B. beim Kombinieren der Funktionalität verschiedener Bibliotheken) ist es jedoch zweckmäßig, einen solchen gemeinsamen Einstiegspunkt zu haben, in dem alle verwendeten Funktionen aufgelistet sind.



Als nächstes kompilieren wir den Code mit dem bereits erwähnten Emscripten- Compiler :



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


Die Größe der empfangenen Artefakte ist alarmierend:



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


Wenn Sie die JS-Dateischicht öffnen, sehen Sie Folgendes:







Es enthält viele unnötige Dinge: von Kommentaren bis zu Servicefunktionen, von denen die meisten nicht verwendet werden. Die Situation kann durch Hinzufügen des Flags -O2

korrigiert werden. Im Emscripten-Compiler enthält es auch die js-Code-Optimierung.

Danach sieht der js-Code besser aus:







Client-Code



Sie müssen den clientseitigen Handler irgendwie aufrufen. Laden Sie zunächst die vom Benutzer bereitgestellte Datei durch FileReader, wir speichern die Rohdaten in einem Grundelement Uint8Array:



var rawData = new Uint8Array(fileReader.result);


Als Nächstes müssen Sie die heruntergeladenen Daten auf die virtuelle Maschine übertragen. Dazu weisen wir zuerst die erforderliche Anzahl von Bytes mit der Methode _malloc zu und kopieren dann das JS-Array mit der Methode set dorthin. Lassen Sie uns diese Logik der Einfachheit halber in die Funktion arrayToWasmPtr (Array) unterteilen:




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


Nachdem Sie die Daten in den Speicher der virtuellen Maschine geladen haben, müssen Sie die Funktion irgendwie aus der Verarbeitung aufrufen. Aber wie findet man diese Funktion? Die cwrap-Methode hilft uns - das erste Argument gibt den Namen der erforderlichen Funktion an, das zweite - den Rückgabetyp und das dritte - eine Liste mit Eingabeargumenten.




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


Schließlich müssen Sie die fertigen Bytes von der virtuellen Maschine zurückgeben. Dazu schreiben wir eine weitere Funktion, die sie mit der Methode in ein JS-Array kopiertsubarray



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


Das vollständige Skript zur Verarbeitung eingehender Dateien finden Sie hier . HTML-Markup mit Formular zum Hochladen von Dateien und Hochladen von Wasm-Artefakten hier .



Ergebnis



Hier können Sie mit dem Prototyp herumspielen .



Das Ergebnis ist eine funktionierende Sicherung mit WASM. Von den Minuspunkten - Die aktuelle Implementierung der Technologie erlaubt es nicht, in der virtuellen Maschine zugewiesenen Speicher freizugeben. Dies führt zu einem impliziten Leck, wenn eine große Anzahl von Dateien in einer Sitzung geladen wird. Dies kann jedoch behoben werden, indem vorhandener Speicher wiederverwendet wird, anstatt neuen zuzuweisen.










All Articles