Wasmer: perpustakaan Go tercepat untuk mengeksekusi kode WebAssembly

WebAssembly (wasm) adalah format instruksi biner portabel. Kode wasm kode yang sama dapat dieksekusi di lingkungan apa pun. Untuk mendukung pernyataan ini, setiap bahasa, platform, dan sistem harus dapat mengeksekusi kode tersebut, membuatnya secepat dan seaman mungkin.


Wasmer adalah runtime wasm yang ditulis dalam Rust . Jelas, wasmer dapat digunakan dalam aplikasi Rust apa pun. Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa ia dan peserta lain dalam proyek Wasmer berhasil mengimplementasikan runtime kode-was ini dalam bahasa lain:


Di sini kita akan berbicara tentang proyek baru - go-ext-wasm , yang merupakan perpustakaan untuk Go, yang dirancang untuk mengeksekusi kode biner-biner. Ternyata, proyek pengembangan-jauh jauh lebih cepat daripada solusi serupa lainnya. Tapi jangan maju dulu. Mari kita mulai dengan sebuah cerita tentang bagaimana bekerja dengannya.

Memanggil fungsi wasme dari Go


Untuk memulai, instal wasmer di lingkungan Go (dengan dukungan cgo).

export CGO_ENABLED=1; export CC=gcc; go install github.com/wasmerio/go-ext-wasm/wasmer 

Proyek go-ext-wasm adalah perpustakaan Go biasa. Saat bekerja dengan perpustakaan ini, konstruk import "github.com/wasmerio/go-ext-wasm/wasmer" .

Sekarang mari kita berlatih. Kami akan menulis program sederhana yang dikompilasi dalam wasm. Kami akan menggunakan ini, misalnya, Karat:

 #[no_mangle] pub extern fn sum(x: i32, y: i32) -> i32 {   x + y } 

Kami memanggil file dengan program simple.rs , sebagai hasil dari kompilasi program ini kami mendapatkan file simple.wasm .

Program berikut, yang ditulis dalam Go, menjalankan fungsi sum dari file wasm, dengan memberikan angka 5 dan 37 sebagai argumen:

 package main import (   "fmt"   wasm "github.com/wasmerio/go-ext-wasm/wasmer" ) func main() {   //   WebAssembly.   bytes, _ := wasm.ReadBytes("simple.wasm")   //    WebAssembly.   instance, _ := wasm.NewInstance(bytes)   defer instance.Close()   //    `sum`   WebAssembly.   sum := instance.Exports["sum"]   //        Go.   //   ,      ,  .   result, _ := sum(5, 37)   fmt.Println(result) // 42! } 

Di sini, sebuah program yang ditulis dalam Go memanggil fungsi dari file wasm yang diperoleh dengan mengkompilasi kode yang ditulis dalam Rust.

Jadi, percobaan itu berhasil, kami berhasil mengeksekusi kode WebAssembly di Go. Perlu dicatat bahwa konversi tipe data bersifat otomatis. Nilai-nilai Go yang dilewatkan ke kode wasm dilemparkan ke tipe WebAssembly. Apa yang dikembalikan fungsi wasm ke tipe Go. Akibatnya, bekerja dengan fungsi dari file wasm di Go terlihat sama dengan bekerja dengan fungsi Go biasa.

Fungsi Panggilan Pergi dari Kode WebAssembly


Seperti yang kita lihat pada contoh sebelumnya, modul WebAssembly dapat mengekspor fungsi yang dapat dipanggil dari luar. Ini adalah mekanisme yang memungkinkan kode wasme dijalankan di berbagai lingkungan.

Pada saat yang sama, modul WebAssembly sendiri dapat bekerja dengan fungsi yang diimpor. Pertimbangkan program berikut yang ditulis dalam Rust.

 extern {   fn sum(x: i32, y: i32) -> i32; } #[no_mangle] pub extern fn add1(x: i32, y: i32) -> i32 {   unsafe { sum(x, y) } + 1 } 

Beri nama file dengan import.rs . Mengompilasinya ke dalam WebAssembly akan menghasilkan kode yang dapat ditemukan di sini .

Fungsi add1 diekspor add1 fungsi sum . Tidak ada implementasi dari fungsi ini, hanya tanda tangannya yang ditentukan dalam file. Inilah yang disebut fungsi eksternal. Untuk WebAssembly, ini adalah fungsi yang diimpor. Implementasinya harus diimpor.

Kami mengimplementasikan fungsi sum menggunakan Go. Untuk ini, kita perlu menggunakan cgo . Ini kode yang dihasilkan. Beberapa komentar, yang merupakan deskripsi dari fragmen kode utama, diberi nomor. Di bawah ini kita akan membicarakannya secara lebih rinci.

 package main // // 1.    `sum` (   cgo). // // #include <stdlib.h> // // extern int32_t sum(void *context, int32_t x, int32_t y); import "C" import (   "fmt"   wasm "github.com/wasmerio/go-ext-wasm/wasmer"   "unsafe" ) // 2.    `sum`    ( cgo). //export sum func sum(context unsafe.Pointer, x int32, y int32) int32 {   return x + y } func main() {   //   WebAssembly.   bytes, _ := wasm.ReadBytes("import.wasm")   // 3.     WebAssembly.   imports, _ := wasm.NewImports().Append("sum", sum, C.sum)   // 4.     WebAssembly  .   instance, _ := wasm.NewInstanceWithImports(bytes, imports)   //    WebAssembly.   defer instance.Close()   //    `add1`   WebAssembly.   add1 := instance.Exports["add1"]   //   .   result, _ := add1(1, 2)   fmt.Println(result)   // add1(1, 2)   // = sum(1 + 2) + 1   // = 1 + 2 + 1   // = 4   // QED } 

Mari kita uraikan kode ini:

  1. Tanda tangan dari fungsi sum didefinisikan dalam C (lihat komentar pada perintah import "C" ).
  2. Implementasi fungsi sum didefinisikan dalam Go (perhatikan baris //export - mekanisme ini dapat digunakan untuk membuat koneksi kode yang ditulis dalam Go dengan kode yang ditulis dalam C).
  3. NewImports adalah API yang digunakan untuk membuat impor WebAssembly. Dalam kode ini, "sum" adalah nama fungsi yang diimpor oleh WebAssembly, sum adalah penunjuk ke fungsi Go, dan C.sum adalah penunjuk ke fungsi cgo.
  4. Dan akhirnya, NewInstanceWithImports adalah konstruktor yang dirancang untuk menginisialisasi modul WebAssembly dengan impor.

Membaca data dari memori


Contoh WebAssembly memiliki memori linier. Mari kita bicara tentang cara membaca data darinya. Mari kita mulai, seperti biasa, dengan kode Rust, yang akan kita sebut memory.rs .

 #[no_mangle] pub extern fn return_hello() -> *const u8 {   b"Hello, World!\0".as_ptr() } 

Hasil kompilasi kode ini ada di file memory.wasm , yang digunakan di bawah ini.

Fungsi return_hello mengembalikan pointer ke string. Baris berakhir, seperti dalam C, dengan karakter nol.

Sekarang pergi ke sisi Go:

 bytes, _ := wasm.ReadBytes("memory.wasm") instance, _ := wasm.NewInstance(bytes) defer instance.Close() //    `return_hello`. //      . result, _ := instance.Exports["return_hello"]() //      . pointer := result.ToI32() //    . memory := instance.Memory.Data() fmt.Println(string(memory[pointer : pointer+13])) // Hello, World! 

Fungsi return_hello mengembalikan pointer sebagai nilai i32 . Kami mendapatkan nilai ini dengan menghubungi ToI32 . Kemudian kita mendapatkan data dari memori menggunakan instance.Memory.Data() .

Fungsi ini mengembalikan potongan memori instance WebAssembly. Anda dapat menggunakannya seperti slice Go.

Untungnya, kita tahu panjang baris yang ingin kita baca, oleh karena itu, untuk membaca informasi yang diperlukan, cukup menggunakan memory[pointer : pointer+13] . Kemudian data yang dibaca dikonversi menjadi string.

Berikut adalah contoh yang menunjukkan mekanisme memori yang lebih maju saat menggunakan kode WebAssembly Go.

Tingkatan yang dicapai


Proyek go-ext-wasm, seperti yang baru saja kita lihat, memiliki API yang nyaman. Sekarang saatnya berbicara tentang kinerjanya.

Tidak seperti PHP atau Ruby, dunia Go sudah memiliki solusi untuk bekerja dengan kode wasm. Secara khusus, kita berbicara tentang proyek-proyek berikut:

  • Life from Perlin Network - Juru bahasa WebAssembly.
  • Go Interpreter's Wagon adalah interpreter dan toolkit WebAssembly.

Materi pada proyek php-ext-wasm menggunakan algoritma n-body untuk mempelajari kinerja. Ada banyak algoritma lain yang cocok untuk memeriksa kinerja lingkungan eksekusi kode. Sebagai contoh, ini adalah algoritma Fibonacci (versi rekursif) dan algoritma Pollard ฯ yang digunakan dalam Life. Ini adalah algoritma kompresi Snappy. Yang terakhir ini bekerja dengan sukses dengan go-ext-wasm, tetapi tidak dengan Life atau Wagon. Akibatnya, ia dikeluarkan dari set tes. Kode uji dapat ditemukan di sini .

Selama pengujian, versi terbaru dari proyek penelitian digunakan. Yaitu, ini adalah Life 20190521143330-57f3819c2df0 dan Wagon 0.4.0.

Angka-angka yang ditunjukkan pada grafik mencerminkan nilai rata-rata yang diperoleh setelah 10 awal tes. Studi ini menggunakan 2016 MacBook Pro 15 "dengan prosesor Intel Core i7 2,9 GHz dan memori 16 GB.

Hasil tes dikelompokkan sepanjang sumbu X sesuai dengan jenis tes. Sumbu Y menunjukkan waktu, dalam milidetik, yang diperlukan untuk menyelesaikan tes. Semakin kecil indikatornya, semakin baik.


Perbandingan kinerja Wasmer, Wagon dan Life menggunakan implementasi dari berbagai algoritma

Platform Life and Wagon, secara rata-rata, memberikan hasil yang hampir sama. Wasmer, rata-rata, adalah 72 kali lebih cepat.

Penting untuk dicatat bahwa Wasmer mendukung tiga backend: Singlepass , Cranelift dan LLVM . Backend default di Go library adalah Cranelift (di sini Anda dapat mengetahui lebih lanjut tentang itu). Menggunakan LLVM akan memberikan kinerja yang mendekati aslinya, tetapi diputuskan untuk memulai dengan Cranelift, karena backend ini memberikan rasio terbaik antara waktu kompilasi dan waktu eksekusi program.

Di sini Anda dapat membaca tentang berbagai backend, pro dan kontra mereka, dan dalam situasi apa lebih baik menggunakannya.

Ringkasan


Proyek open source go-ext-wasm adalah Go library baru yang dirancang untuk mengeksekusi kode biner wasme. Ini termasuk runtime Wasmer . Versi pertamanya termasuk API, kebutuhan yang paling sering muncul.
Tes kinerja menunjukkan bahwa Wasmer, rata-rata, 72 kali lebih cepat daripada Life and Wagon.

Pembaca yang budiman! Apakah Anda berencana menggunakan kemampuan untuk menjalankan kode wasme di Go menggunakan go-ext-wasm?

Source: https://habr.com/ru/post/id454518/


All Articles