Primeira introdução ao AssemblyScript

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?


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


All Articles