WebAssembly (wasm) est un format d'instruction binaire portable. Le même code wasm peut être exécuté dans n'importe quel environnement. Afin de prendre en charge cette déclaration, chaque langue, plate-forme et système doit être capable d'exécuter un tel code, le rendant aussi rapide et sûr que possible.
Wasmer est un runtime de wasm écrit en
Rust . De toute évidence, le wasmer peut être utilisé dans n'importe quelle application Rust. L'auteur du document, dont nous publions la traduction aujourd'hui, dit que lui et d'autres participants au projet Wasmer ont mis en œuvre avec succès ce runtime de code wasm dans d'autres langues:
Ici, nous parlerons d'un nouveau projet -
go-ext-wasm , qui est une bibliothèque pour Go, conçue pour exécuter du code wasm binaire. Il s'est avéré que le projet go-ext-wasm est beaucoup plus rapide que d'autres solutions similaires. Mais n'allons pas de l'avant. Commençons par une histoire sur la façon de travailler avec lui.
Appel des fonctions de wasm à partir de Go
Pour commencer, installez wasmer dans un environnement Go (avec support cgo).
export CGO_ENABLED=1; export CC=gcc; go install github.com/wasmerio/go-ext-wasm/wasmer
Le projet
go-ext-wasm est une bibliothèque Go classique. Lorsque vous travaillez avec cette bibliothèque, la construction d'
import "github.com/wasmerio/go-ext-wasm/wasmer"
.
Passons maintenant à la pratique. Nous allons écrire un programme simple qui se compile en wasm. Nous utiliserons pour cela, par exemple, Rust:
#[no_mangle] pub extern fn sum(x: i32, y: i32) -> i32 { x + y }
Nous appelons le fichier avec le programme
simple.rs
, à la suite de la compilation de ce programme, nous obtenons le fichier
simple.wasm .
Le programme suivant, écrit en Go, exécute la fonction
sum
du fichier wasm, en lui passant les nombres 5 et 37 comme arguments:
package main import ( "fmt" wasm "github.com/wasmerio/go-ext-wasm/wasmer" ) func main() {
Ici, un programme écrit en Go appelle une fonction à partir d'un fichier wasm qui a été obtenu en compilant du code écrit en Rust.
L'expérience a donc été un succès, nous avons réussi à exécuter le code WebAssembly dans Go. Il convient de noter que la conversion des types de données est automatisée. Les valeurs Go transmises au code wasm sont converties en types WebAssembly. Ce que la fonction wasm renvoie est converti en types Go. Par conséquent, travailler avec des fonctions à partir de fichiers wasm dans Go ressemble à travailler avec des fonctions Go normales.
Fonctions d'appel à partir du code WebAssembly
Comme nous l'avons vu dans l'exemple précédent, les modules WebAssembly sont capables d'exporter des fonctions qui peuvent être appelées de l'extérieur. C'est le mécanisme qui permet au code wasm d'être exécuté dans divers environnements.
Dans le même temps, les modules WebAssembly eux-mêmes peuvent fonctionner avec des fonctions importées. Considérez le programme suivant écrit en 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 }
Nommez le fichier avec
import.rs
. Le compiler dans WebAssembly se traduira par du code qui peut être trouvé
ici .
La fonction
add1
exportée appelle la fonction
sum
. Il n'y a pas d'implémentation de cette fonction, seule sa signature est définie dans le fichier. Il s'agit de la fonction dite externe. Pour WebAssembly, il s'agit d'une fonction importée. Son implémentation doit être importée.
Nous implémentons la fonction
sum
utilisant Go. Pour cela, nous devons utiliser
cgo . Voici le code résultant. Certains commentaires, qui sont des descriptions des principaux fragments de code, sont numérotés. Ci-dessous, nous en parlerons plus en détail.
package main
Analysons ce code:
- La signature de la fonction
sum
est définie en C (voir le commentaire sur la commande d' import "C"
). - L'implémentation de la fonction
sum
est définie dans Go (notez la ligne //export
- ce mécanisme utilisé par cgo pour établir la connexion du code écrit en Go avec le code écrit en C). NewImports
est une API utilisée pour créer des importations WebAssembly. Dans ce code, "sum"
est le nom de la fonction importée par WebAssembly, sum
est le pointeur vers la fonction Go et C.sum
est le pointeur vers la fonction cgo.- Et enfin,
NewInstanceWithImports
est un constructeur conçu pour initialiser un module WebAssembly avec des importations.
Lecture des données de la mémoire
L'instance WebAssembly a une mémoire linéaire. Parlons de la façon d'en lire les données. Commençons, comme d'habitude, avec le code Rust, que nous appellerons
memory.rs
.
#[no_mangle] pub extern fn return_hello() -> *const u8 { b"Hello, World!\0".as_ptr() }
Le résultat de la compilation de ce code se trouve dans le fichier
memory.wasm
, qui est utilisé ci-dessous.
La fonction
return_hello
renvoie un pointeur sur une chaîne. La ligne se termine, comme en C, par un caractère nul.
Maintenant, allez sur le côté Go:
bytes, _ := wasm.ReadBytes("memory.wasm") instance, _ := wasm.NewInstance(bytes) defer instance.Close()
La fonction
return_hello
renvoie un pointeur en tant que valeur
i32
. Nous obtenons cette valeur en appelant
ToI32
. Ensuite, nous obtenons les données de la mémoire en utilisant
instance.Memory.Data()
.
Cette fonction renvoie la tranche de mémoire de l'instance WebAssembly. Vous pouvez l'utiliser comme n'importe quelle tranche Go.
Heureusement, nous connaissons la longueur de la ligne que nous voulons lire, donc, pour lire les informations nécessaires, il suffit d'utiliser la construction
memory[pointer : pointer+13]
. Ensuite, les données lues sont converties en chaîne.
Voici un exemple qui montre des mécanismes de mémoire plus avancés lors de l'utilisation du code WebAssembly de Go.
Repères
Le projet go-ext-wasm, comme nous venons de le voir, dispose d'une API pratique. Il est maintenant temps de parler de ses performances.
Contrairement à PHP ou Ruby, le monde Go a déjà des solutions pour travailler avec du code wasm. En particulier, nous parlons des projets suivants:
- Life from Perlin Network - Interprète WebAssembly.
- Go Interpreter's Wagon est un interpréteur et une boîte à outils WebAssembly.
Le
matériel du projet php-ext-wasm a utilisé l'algorithme à
n corps pour étudier les performances. Il existe de nombreux autres algorithmes adaptés à l'examen des performances des environnements d'exécution de code. Par exemple, il s'agit de l'algorithme de
Fibonacci (version récursive) et de
l'algorithme ρ de Pollard utilisé dans Life. Il s'agit de l'algorithme de compression Snappy. Ce dernier fonctionne avec succès avec go-ext-wasm, mais pas avec Life ou Wagon. En conséquence, il a été retiré de l'ensemble de test. Le code de test peut être trouvé
ici .
Lors des tests, les dernières versions des projets de recherche ont été utilisées. À savoir, ce sont Life 20190521143330-57f3819c2df0 et Wagon 0.4.0.
Les nombres indiqués sur le graphique reflètent les valeurs moyennes obtenues après 10 démarrages du test. L'étude a utilisé le MacBook Pro 15 pouces 2016 avec un processeur Intel Core i7 2,9 GHz et 16 Go de mémoire.
Les résultats des tests sont regroupés le long de l'axe X en fonction des types de tests. L'axe des Y indique le temps, en millisecondes, nécessaire pour terminer le test. Plus l'indicateur est petit, mieux c'est.
Comparaison des performances de Wasmer, Wagon et Life à l'aide d'implémentations de divers algorithmesLes plateformes Life et Wagon, en moyenne, donnent à peu près les mêmes résultats. Wasmer, en moyenne, est 72 fois plus rapide.
Il est important de noter que Wasmer prend en charge trois
backends :
Singlepass ,
Cranelift et
LLVM . Le backend par défaut dans la bibliothèque Go est Cranelift (
ici vous pouvez en savoir plus). L'utilisation de LLVM donnera des performances proches de celles natives, mais il a été décidé de commencer par Cranelift, car ce backend donne le meilleur rapport entre le temps de compilation et le temps d'exécution du programme.
Ici, vous pouvez lire les différents backends, leurs avantages et leurs inconvénients, et dans quelles situations il est préférable de les utiliser.
Résumé
Le projet open source
go-ext-wasm est une nouvelle bibliothèque Go conçue pour exécuter du code wasm binaire. Il comprend un
runtime Wasmer . Sa première version comprend des API, dont le besoin se fait le plus souvent sentir.
Les tests de performance ont montré que Wasmer, en moyenne, est 72 fois plus rapide que Life et Wagon.
Chers lecteurs! Envisagez-vous d'utiliser la possibilité d'exécuter du code wasm dans Go à l'aide de go-ext-wasm?
