O WebAssembly (wasm) é um formato de instrução binária portátil. O mesmo código wasm code pode ser executado em qualquer ambiente. Para suportar esta declaração, todo idioma, plataforma e sistema deve poder executar esse código, tornando-o o mais rápido e seguro possível.
Wasmer é um tempo de execução wasm escrito em
Rust . Obviamente, o wasmer pode ser usado em qualquer aplicação Rust. O autor do material, cuja tradução publicamos hoje, diz que ele e outros participantes do projeto Wasmer implementaram com êxito esse tempo de execução do código wasm em outros idiomas:
Aqui falaremos sobre um novo projeto -
go-ext-wasm , que é uma biblioteca para Go, projetada para executar código-binário wasm. Como se viu, o projeto go-ext-wasm é muito mais rápido que outras soluções semelhantes. Mas não vamos nos antecipar. Vamos começar com uma história sobre como trabalhar com ele.
Chamando funções wasm do Go
Para começar, instale o wasmer em um ambiente Go (com suporte ao cgo).
export CGO_ENABLED=1; export CC=gcc; go install github.com/wasmerio/go-ext-wasm/wasmer
O projeto
go-ext-wasm é uma biblioteca Go normal. Ao trabalhar com esta biblioteca, a construção de
import "github.com/wasmerio/go-ext-wasm/wasmer"
.
Agora vamos praticar. Vamos escrever um programa simples que compila no wasm. Usaremos para isso, por exemplo, Rust:
#[no_mangle] pub extern fn sum(x: i32, y: i32) -> i32 { x + y }
Chamamos o arquivo com o programa
simple.rs
; como resultado da compilação desse programa, obtemos o arquivo
simple.wasm .
O programa a seguir, escrito em Go, executa a função
sum
do arquivo wasm, passando os números 5 e 37 como argumentos:
package main import ( "fmt" wasm "github.com/wasmerio/go-ext-wasm/wasmer" ) func main() {
Aqui, um programa escrito em Go chama uma função de um arquivo wasm que foi obtido pela compilação de código escrito em Rust.
Portanto, o experimento foi um sucesso. Executamos com êxito o código do WebAssembly no Go. Note-se que a conversão do tipo de dados é automatizada. Esses valores de Go que são transmitidos para o código wasm são convertidos para tipos do WebAssembly. O que a função wasm retorna é convertido para tipos Go. Como resultado, trabalhar com funções de arquivos wasm no Go é o mesmo que trabalhar com funções normais do Go.
Funções Call Go a partir do código do WebAssembly
Como vimos no exemplo anterior, os módulos WebAssembly podem exportar funções que podem ser chamadas de fora. Esse é o mecanismo que permite que o código wasm seja executado em vários ambientes.
Ao mesmo tempo, os próprios módulos WebAssembly podem funcionar com funções importadas. Considere o seguinte programa escrito em 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 }
Nomeie o arquivo com
import.rs
. A compilação no WebAssembly resultará em um código que pode ser encontrado
aqui .
A função
add1
exportada chama a função
sum
. Não há implementação dessa função, apenas sua assinatura é definida no arquivo. Essa é a chamada função externa. Para o WebAssembly, essa é uma função importada. Sua implementação deve ser importada.
Implementamos a função
sum
usando Go. Para isso, precisamos usar o
cgo . Aqui está o código resultante. Alguns comentários, que são descrições dos principais fragmentos de código, são numerados. Abaixo falaremos sobre eles em mais detalhes.
package main
Vamos analisar este código:
- A assinatura da função
sum
é definida em C (consulte o comentário no comando import "C"
). - A implementação da função
sum
é definida em Go (observe a linha //export
- este mecanismo que o cgo usa para estabelecer a conexão do código escrito em Go com o código escrito em C). NewImports
é uma API usada para criar importações do WebAssembly. Nesse código, "sum"
é o nome da função importada pelo WebAssembly, sum
é o ponteiro para a função Go e C.sum
é o ponteiro para a função cgo.- E, finalmente,
NewInstanceWithImports
é um construtor projetado para inicializar um módulo WebAssembly com importações.
Lendo dados da memória
A instância do WebAssembly possui memória linear. Vamos falar sobre como ler dados dele. Vamos começar, como sempre, com o código Rust, que chamaremos de
memory.rs
.
#[no_mangle] pub extern fn return_hello() -> *const u8 { b"Hello, World!\0".as_ptr() }
O resultado da compilação desse código está no arquivo
memory.wasm
, usado abaixo.
A função
return_hello
retorna um ponteiro para uma string. A linha termina, como em C, com um caractere nulo.
Agora vá para o lado Ir:
bytes, _ := wasm.ReadBytes("memory.wasm") instance, _ := wasm.NewInstance(bytes) defer instance.Close()
A função
return_hello
retorna um ponteiro como um valor
i32
.
ToI32
esse valor chamando
ToI32
. Em seguida, obtemos os dados da memória usando
instance.Memory.Data()
.
Esta função retorna a fatia de memória da instância do WebAssembly. Você pode usá-lo como qualquer fatia do Go.
Felizmente, sabemos o comprimento da linha que queremos ler, portanto, para ler as informações necessárias, basta usar o construto
memory[pointer : pointer+13]
. Em seguida, os dados lidos são convertidos em uma sequência.
Aqui está um exemplo que mostra mecanismos de memória mais avançados ao usar o código WebAssembly da Go.
Benchmarks
O projeto go-ext-wasm, como acabamos de ver, possui uma API conveniente. Agora é hora de falar sobre seu desempenho.
Diferente do PHP ou Ruby, o mundo Go já tem soluções para trabalhar com código wasm. Em particular, estamos falando dos seguintes projetos:
- Vida da rede Perlin - intérprete do WebAssembly.
- O vagão da Go Interpreter é um interpretador e kit de ferramentas do WebAssembly.
O
material do projeto php-ext-wasm usou o algoritmo
n-body para estudar o desempenho. Existem muitos outros algoritmos adequados para examinar o desempenho de ambientes de execução de código. Por exemplo, este é o algoritmo de
Fibonacci (versão recursiva) e
o algoritmo Pollard ρ usado no Life. Este é o algoritmo de compactação Snappy. O último trabalha com êxito com go-ext-wasm, mas não com Life ou Wagon. Como resultado, ele foi removido do conjunto de testes. O código de teste pode ser encontrado
aqui .
Durante os testes, foram utilizadas as versões mais recentes dos projetos de pesquisa. Ou seja, são Life 20190521143330-57f3819c2df0 e Wagon 0.4.0.
Os números mostrados no gráfico refletem os valores médios obtidos após 10 partidas do teste. O estudo usou o 2016 MacBook Pro 15 "com um processador Intel Core i7 2,9 GHz e 16 GB de memória.
Os resultados dos testes são agrupados ao longo do eixo X de acordo com os tipos de testes. O eixo Y mostra o tempo, em milissegundos, necessário para concluir o teste. Quanto menor o indicador, melhor.
Comparação de desempenho de Wasmer, Wagon e Life usando implementações de vários algoritmosAs plataformas Life e Wagon, em média, fornecem aproximadamente os mesmos resultados. O Wasmer, em média, é 72 vezes mais rápido.
É importante notar que o Wasmer suporta três back-ends:
Singlepass ,
Cranelift e
LLVM . O back-end padrão na biblioteca Go é Cranelift (
aqui você pode descobrir mais sobre ele). O uso do LLVM fornecerá um desempenho próximo do nativo, mas foi decidido começar com o Cranelift, pois esse back-end fornece a melhor relação entre o tempo de compilação e o tempo de execução do programa.
Aqui você pode ler sobre diferentes back-ends, seus prós e contras e em quais situações é melhor usá-los.
Sumário
O projeto de código aberto
go-ext-wasm é uma nova biblioteca Go projetada para executar o código binário do wasm. Inclui um
tempo de execução do Wasmer . Sua primeira versão inclui APIs, cuja necessidade surge com mais frequência.
Testes de desempenho mostraram que o Wasmer, em média, é 72 vezes mais rápido que o Life e o Wagon.
Caros leitores! Você planeja usar a capacidade de executar o código wasm no Go usando go-ext-wasm?
