
WebAssembly (disingkat WASM) adalah teknologi untuk menjalankan kode biner yang telah dikompilasi sebelumnya di browser di sisi klien. Ini pertama kali diperkenalkan pada tahun 2015 dan saat ini didukung oleh sebagian besar browser modern.
Salah satu kasus penggunaan yang umum adalah pemrosesan awal data di sisi klien sebelum mengirim file ke server. Pada artikel ini, kami akan memahami bagaimana ini dilakukan.
Sebelum memulai
Arsitektur WebAssembly dan langkah-langkah umum dijelaskan lebih detail di sini dan di sini . Kami hanya akan membahas fakta-fakta dasar.
Bekerja dengan WebAssembly dimulai dengan pra-perakitan artefak yang diperlukan untuk menjalankan kode yang dikompilasi di sisi klien. Ada dua di antaranya: file WASM biner itu sendiri dan lapisan JavaScript di mana Anda dapat memanggil metode yang diekspor ke sana.
Contoh kode C ++ paling sederhana untuk kompilasi
#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;
}
}
Untuk perakitan, Emscripten digunakan , yang, selain antarmuka utama dari kompiler tersebut, berisi tanda tambahan yang melaluinya konfigurasi mesin virtual dan metode yang diekspor ditetapkan. Peluncuran paling sederhana terlihat seperti ini:
em++ main.cpp --std=c++17 -o gcd.html \
-s EXPORTED_FUNCTIONS='["_calculate_gcd"]' \
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
Dengan menentukan file * .html sebagai objek , ia memberitahu kompiler untuk membuat markup html sederhana dengan konsol js juga. Sekarang, jika kita memulai server pada file yang diterima, kita akan melihat konsol ini dengan kemampuan untuk meluncurkan _calculate_gcd:

Pengolahan data
Mari kita analisis menggunakan contoh sederhana kompresi lz4 menggunakan pustaka yang ditulis dalam C ++. Perhatikan bahwa banyak bahasa yang didukung tidak berakhir di situ.
Terlepas dari kesederhanaan dan beberapa sifat sintetis dari contohnya, ini adalah ilustrasi yang cukup berguna tentang bagaimana bekerja dengan data. Demikian pula, Anda dapat melakukan tindakan apa pun pada mereka yang daya kliennya mencukupi: pemrosesan awal gambar sebelum mengirim ke server, kompresi audio, menghitung berbagai statistik, dan banyak lagi.
Seluruh kode dapat ditemukan di sini.
Bagian C ++
Kami menggunakan implementasi lz4 yang sudah jadi . Maka file utama akan terlihat sangat singkat:
#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;
}
}
Seperti yang Anda lihat, itu hanya mendeklarasikan fungsi eksternal (menggunakan kata kunci extern ) yang secara internal memanggil metode yang sesuai dari perpustakaan dengan lz4.
Secara umum, dalam kasus kami, file tidak berguna: Anda dapat langsung menggunakan antarmuka asli lz4.h . Namun, dalam proyek yang lebih kompleks (misalnya, menggabungkan fungsionalitas pustaka yang berbeda), akan lebih mudah untuk memiliki titik masuk umum yang mencantumkan semua fungsi yang digunakan.
Selanjutnya, kami mengkompilasi kode menggunakan kompiler Emscripten yang telah disebutkan :
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
Ukuran artefak yang diterima mengkhawatirkan:
$ du -hs wasm_compressor.*
112K wasm_compressor.js
108K wasm_compressor.wasm
Jika Anda membuka lapisan file JS, Anda dapat melihat sesuatu seperti berikut:

Ini berisi banyak hal yang tidak perlu: dari komentar hingga fungsi layanan, yang sebagian besar tidak digunakan. Situasi ini dapat diperbaiki dengan menambahkan tanda -O2, dalam kompiler Emscripten, ini juga termasuk optimasi kode js.
Setelah itu, kode js terlihat lebih bagus:

Kode klien
Anda harus memanggil penangan sisi klien entah bagaimana. Pertama-tama, muat file yang disediakan oleh pengguna melalui
FileReader, kami akan menyimpan data mentah secara primitif Uint8Array:
var rawData = new Uint8Array(fileReader.result);
Selanjutnya, Anda perlu mentransfer data yang diunduh ke mesin virtual. Untuk melakukan ini, pertama-tama kita mengalokasikan jumlah byte yang diperlukan menggunakan metode _malloc, lalu menyalin larik JS di sana menggunakan metode set. Untuk kenyamanan, mari pisahkan logika ini ke dalam fungsi arrayToWasmPtr (array):
function arrayToWasmPtr(array) {
var ptr = Module._malloc(array.length);
Module.HEAP8.set(array, ptr);
return ptr;
}
Setelah memuat data ke dalam memori mesin virtual, Anda harus memanggil fungsi tersebut dari pemrosesan. Tetapi bagaimana menemukan fungsi ini? Metode cwrap akan membantu kita - argumen pertama di dalamnya menentukan nama fungsi yang diperlukan, yang kedua - tipe kembalian, dan yang ketiga - daftar dengan argumen input.
compressDataFunction = Module.cwrap('compress_data', 'number', ['number', 'number', 'number']);
Terakhir, Anda perlu mengembalikan byte yang sudah selesai dari mesin virtual. Untuk melakukan ini, kami menulis fungsi lain yang menyalinnya ke dalam array JS menggunakan metode
subarray
function wasmPtrToArray(ptr, length) {
var array = new Int8Array(length);
array.set(Module.HEAP8.subarray(ptr, ptr + length));
return array;
}
Skrip lengkap untuk memproses file yang masuk ada di sini . Markup HTML berisi formulir unggah file dan artefak wasm diunggah di sini .
Hasil
Anda dapat bermain-main dengan prototipe di sini .
Hasilnya adalah backup yang berfungsi menggunakan WASM. Dari minus - implementasi teknologi saat ini tidak memungkinkan membebaskan memori yang dialokasikan dalam mesin virtual. Ini menciptakan kebocoran implisit ketika sejumlah besar file dimuat dalam satu sesi, tetapi dapat diperbaiki dengan menggunakan kembali memori yang ada daripada mengalokasikan yang baru.
