O suporte à
tecnologia WebAssembly (Wasm) apareceu nos navegadores há relativamente pouco tempo. Mas essa tecnologia pode expandir os recursos da Web, tornando-a uma plataforma capaz de suportar aplicativos que geralmente são vistos como desktop.
Dominar o WebAssembly pode ser uma tarefa assustadora para desenvolvedores da web. No entanto, o compilador
AssemblyScript pode melhorar a situação.
O autor do artigo, que estamos publicando a tradução de hoje, oferece primeiro para falar sobre por que o WebAssembly é uma tecnologia muito promissora e, em seguida, ver como o AssemblyScript pode ajudar a desbloquear o potencial de Wasm.
Webassembly
O WebAssembly pode ser chamado de idioma de baixo nível para navegadores. Ele oferece aos desenvolvedores a capacidade de criar código compilado em algo que não seja JavaScript. Isso permite que os programas incluídos nas páginas da Web funcionem quase tão rápido quanto os aplicativos nativos para várias plataformas. Esses programas são executados em um ambiente limitado e seguro.
Representantes das equipes responsáveis pelo desenvolvimento de todos os principais navegadores (Chrome, Firefox, Safari e Edge) participaram da criação do padrão WebAssembly. Eles concordaram em uma
arquitetura de sistema no início de 2017. Agora, todos os navegadores acima oferecem suporte ao WebAssembly. De fato, essa tecnologia pode ser usada em
aproximadamente 87% dos navegadores.
O código do WebAssembly existe em formato binário. Isso significa que esse código é menor que o código JavaScript semelhante e é carregado mais rapidamente. Além disso, o código Wasm pode ser apresentado em
formato de texto , para que as pessoas possam ler e editar.
Quando o padrão WebAssembly apareceu pela primeira vez, alguns desenvolvedores pensaram que ele poderia substituir o JavaScript e se tornar o idioma principal da web. Mas o WebAssembly é melhor visto como uma nova ferramenta que se integra bem à plataforma da web existente. Este é um dos seus
objetivos prioritários .
Em vez de substituir o JavaScript, onde esse idioma é usado há muito tempo e com sucesso, o WebAssembly oferece novas oportunidades interessantes para desenvolvedores da web. É verdade que o código Wasm não tem acesso direto ao DOM, portanto, a maioria dos projetos da Web existentes continuará usando JavaScript. Essa linguagem, ao longo dos anos de desenvolvimento e otimização, já é bastante rápida. E o WebAssembly tem seus próprios
aplicativos :
- Jogos
- Cálculos científicos, visualizações, simulações.
- Aplicações CAD.
- Editando imagens e vídeos.
Todos esses usos para Wasm são unidos pelo que seus respectivos aplicativos geralmente são considerados desktop. Porém, devido ao fato do WebAssembly permitir que você atinja um nível de desempenho próximo ao nativo, muitos aplicativos semelhantes agora podem ser implementados usando a plataforma da web.
O WebAssembly pode tirar proveito dos projetos da web existentes. Um exemplo é o projeto
Figma . Graças ao uso do Wasm, o tempo de carregamento deste projeto foi significativamente aprimorado. Se um site usa código que executa cálculos pesados, então, para melhorar o desempenho deste site, faz sentido substituir apenas esse código por um análogo do WebAssembly.
Você pode tentar usar o WebAssembly em seus próprios projetos. Este idioma pode ser aprendido e escrito
imediatamente nele . Porém, o WebAssembly foi desenvolvido originalmente como um
destino de compilação para outros idiomas. Foi
projetado com bom suporte para C e C ++. O
suporte experimental Wasm apareceu no Go 1.11. Está sendo feito muito esforço para escrever aplicativos Wasm no
Rust .
Mas é perfeitamente possível que os desenvolvedores da Web não desejem aprender C, C ++, Go ou Rust apenas para usar o WebAssembly. O que eles fazem? A resposta a esta pergunta pode dar ao AssemblyScript.
AssemblyScript
O AssemblyScript é um compilador que converte o código TypeScript em código do WebAssembly. TypeScript é uma linguagem desenvolvida pela Microsoft. Este é um superconjunto de JavaScript, com suporte aprimorado a tipos e alguns outros recursos. O TypeScript se tornou uma linguagem bastante
popular . Note-se que o AssemblyScript pode converter em Wasm apenas um conjunto limitado de construções TypeScript. Isso significa que mesmo alguém que não conhece o TypeScript pode aprender rapidamente essa linguagem em um nível suficiente para escrever o código que o AssemblyScript entende.
Além disso, como o TypeScript é muito semelhante ao JavaScript, podemos dizer que a tecnologia AssemblyScript permite que os desenvolvedores da Web integrem facilmente módulos Wasm em seus projetos sem a necessidade de aprender uma linguagem completamente nova.
Exemplo
Vamos escrever nosso primeiro módulo AssemblyScript. Todo o código que discutiremos agora pode ser encontrado no
GitHub . Para dar
suporte ao WebAssembly, precisamos pelo menos do
Node.js 8.
Crie um novo diretório, inicialize o projeto npm e instale o AssemblyScript:
mkdir assemblyscript-demo cd assemblyscript-demo npm init npm install --save-dev github:AssemblyScript/assemblyscript
Observe que o AssemblyScript deve ser instalado diretamente
no repositório GitHub do projeto. O AssemblyScript ainda não foi publicado no npm, pois os desenvolvedores
ainda não o consideram pronto para uso generalizado.
asinit
arquivos auxiliares usando o comando
asinit
incluído no AssemblyScript:
npx asinit .
Agora a seção de
scripts
do nosso
package.json
deve assumir o seguinte formato:
{ "scripts": { "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug", "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize", "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized" } }
O arquivo
index.js
localizado na pasta raiz do projeto terá a seguinte aparência:
const fs = require("fs"); const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm")); const imports = { env: { abort(_msg, _file, line, column) { console.error("abort called at index.ts:" + line + ":" + column); } } }; Object.defineProperty(module, "exports", { get: () => new WebAssembly.Instance(compiled, imports).exports });
Isso permite incluir módulos WebAssembly no seu código usando o comando
require . Ou seja - da mesma maneira que os módulos JavaScript regulares estão conectados.
A pasta de
assembly
contém o arquivo
index.ts
. Possui código-fonte escrito de acordo com as regras do AssemblyScript. O código clichê gerado automaticamente é uma função simples para adicionar dois números:
export function add(a: i32, b: i32): i32 { return a + b; }
Talvez você esperasse que a assinatura de uma função semelhante se parecesse com
add(a: number, b: number): number
. Portanto, parece que ele foi escrito em TypeScript comum. Mas aqui, em vez do
number
do tipo, o tipo
i32
. Isso acontece porque o código AssemblyScript usa
tipos WebAssembly específicos para números inteiros e de ponto flutuante, em vez do tipo de
número genérico do TypeScript.
Vamos montar o projeto:
npm run asbuild
Os seguintes arquivos devem aparecer na pasta de
build
:
optimized.wasm optimized.wasm.map optimized.wat untouched.wasm untouched.wasm.map untouched.wat
Existem versões otimizadas e regulares da montagem. Cada versão do assembly nos fornece um arquivo .wasm binário, um
mapa .wasm.map do
mapa e uma representação textual do código binário em um arquivo .wat. A apresentação de teste do código Wasm é destinada ao programador, mas nem olharemos para este arquivo. Na verdade, uma das razões para usar o AssemblyScript é que elimina a necessidade de trabalhar com o código Wasm.
Agora, vamos executar o Node.js no modo REPL e garantir que o módulo Wasm compilado possa ser usado da mesma maneira que qualquer módulo JS comum:
$ node Welcome to Node.js v12.10.0. Type ".help" for more information. > const add = require('./index').add; undefined > add(3, 5) 8
Em geral, isso é tudo o que é necessário para usar a tecnologia WebAssembly no Node.js.
Equipando um projeto com um script observador
Para reconstruir automaticamente o módulo durante o desenvolvimento ao fazer alterações, recomendo usar o pacote
onchange . O fato é que o AssemblyScript
ainda não possui seu próprio sistema para monitorar alterações no arquivo. Instale o pacote onchange:
npm install --save-dev onchange
Adicione o script
asbuild:watch
ao
package.json
.
O sinalizador -i
é incluído no comando para que o processo de construção inicie uma vez quando o script for chamado, antes que ocorram eventos.
{ "scripts": { "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug", "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize", "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", "asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild" } }
Agora, em vez de executar constantemente o comando
asbuild
, basta executar o
asbuild:watch
uma vez.
Desempenho
Escreveremos um teste simples que avaliará o nível de desempenho do código Wasm. O principal escopo do WebAssembly é resolver tarefas que usam o processador intensivamente. Por exemplo, esses são alguns tipos de cálculos "pesados". Vamos criar uma função que descubra se um determinado número é primo.
A implementação básica de JS de uma função semelhante é mostrada abaixo. É organizado de maneira muito simples, verifica o número por força bruta, mas, para nossos propósitos, é adequado, pois realiza grandes quantidades de cálculos.
function isPrime(x) { if (x < 2) { return false; } for (let i = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; }
Uma função semelhante, escrita para o compilador AssemblyScript, parece quase a mesma. A principal diferença é a presença de anotações de tipo no código:
function isPrime(x: u32): bool { if (x < 2) { return false; } for (let i: u32 = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; }
Para analisar o desempenho do código, usaremos o pacote
Benchmark.js . Instale-o:
npm install --save-dev benchmark
Crie o arquivo
benchmark.js
:
const Benchmark = require('benchmark'); const assemblyScriptIsPrime = require('./index').isPrime; function isPrime(x) { for (let i = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; } const suite = new Benchmark.Suite; const startNumber = 2; const stopNumber = 10000; suite.add('AssemblyScript isPrime', function () { for (let i = startNumber; i < stopNumber; i++) { assemblyScriptIsPrime(i); } }).add('JavaScript isPrime', function () { for (let i = startNumber; i < stopNumber; i++) { isPrime(i); } }).on('cycle', function (event) { console.log(String(event.target)); }).on('complete', function () { const fastest = this.filter('fastest'); const slowest = this.filter('slowest'); const difference = (fastest.map('hz') - slowest.map('hz')) / slowest.map('hz') * 100; console.log(`${fastest.map('name')} is ~${difference.toFixed(1)}% faster.`); }).run();
Aqui está o que eu consegui obter após executar o comando
node benchmark
do
node benchmark
no meu computador:
AssemblyScript isPrime x 74.00 ops/sec ±0.43% (76 runs sampled) JavaScript isPrime x 61.56 ops/sec ±0.30% (64 runs sampled) AssemblyScript isPrime is ~20.2% faster.
Como você pode ver, a implementação do algoritmo AssemblyScript foi 20% mais rápida que a implementação do JS. No entanto, observe que este teste é uma
marca de microbench . Não confie demais em seus resultados.
Para encontrar resultados mais confiáveis da pesquisa de desempenho dos projetos AssemblyScript - eu recomendo dar uma olhada
neste e
neste benchmark.
Usando um módulo Wasm em uma página da Web
Vamos usar o nosso módulo Wasm em uma página da web. Começamos criando um arquivo
index.html
com o seguinte conteúdo:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>AssemblyScript isPrime demo</title> </head> <body> <form id="prime-checker"> <label for="number">Enter a number to check if it is prime:</label> <input name="number" type="number" /> <button type="submit">Submit</button> </form> <p id="result"></p> <script src="demo.js"></script> </body> </html>
Agora crie o arquivo
demo.js
cujo código é mostrado abaixo. Existem
várias maneiras de carregar os módulos WebAssembly. O mais eficiente é compilar e inicializar no modo de streaming usando a função
WebAssembly.instantiateStreaming . Observe que precisaremos redefinir a função
abort , que é chamada se alguma
instrução não
for executada.
(async () => { const importObject = { env: { abort(_msg, _file, line, column) { console.error("abort called at index.ts:" + line + ":" + column); } } }; const module = await WebAssembly.instantiateStreaming( fetch("build/optimized.wasm"), importObject ); const isPrime = module.instance.exports.isPrime; const result = document.querySelector("#result"); document.querySelector("#prime-checker").addEventListener("submit", event => { event.preventDefault(); result.innerText = ""; const number = event.target.elements.number.value; result.innerText = `${number} is ${isPrime(number) ? '' : 'not '}prime.`; }); })();
Em seguida, instale o pacote do
servidor estático . Precisamos de um servidor devido ao fato de que, para usar a função
WebAssembly.instantiateStreaming
, o módulo deve ser atendido usando o tipo MIME
application/wasm
.
npm install --save-dev static-server
Adicione o script apropriado ao
package.json
:
{ "scripts": { "serve-demo": "static-server" } }
Agora
npm run serve-demo
e abra o URL do host local no navegador. Se você digitar um determinado número no formulário, poderá descobrir se é simples ou não. Agora, ao desenvolver o AssemblyScript, percorremos um longo caminho desde a escrita do código até seu uso no Node.js. e em uma página da web.
Sumário
O WebAssembly e, portanto, o AssemblyScript, não conseguem, de alguma forma, acelerar qualquer site magicamente. Mas Wasm nunca foi encarregado de acelerar absolutamente tudo. Essa tecnologia é notável, pois abre caminho para a web para novos tipos de aplicativos.
Algo semelhante pode ser dito sobre o AssemblyScript. Essa tecnologia simplifica o acesso ao WebAssembly para um grande número de desenvolvedores. Ele permite, ao criar código em uma linguagem próxima ao JavaScript, usar os recursos do WebAssembly para resolver problemas computacionais complexos.
Caros leitores! Como você avalia as perspectivas de uso do AssemblyScript em seus projetos?
