Primera introducción a AssemblyScript

El soporte para la tecnología WebAssembly (Wasm) ha aparecido en los navegadores relativamente recientemente. Pero esta tecnología puede ampliar las capacidades de la web, convirtiéndola en una plataforma capaz de admitir aplicaciones que generalmente se perciben como escritorio.

Dominar WebAssembly puede ser una tarea desalentadora para los desarrolladores web. Sin embargo, el compilador AssemblyScript puede mejorar la situación.


El autor del artículo, que publicamos la traducción de hoy, ofrece primero hablar sobre por qué WebAssembly es una tecnología muy prometedora, y luego echar un vistazo a cómo AssemblyScript puede ayudar a desbloquear el potencial de Wasm.

Montaje web


WebAssembly puede llamarse un lenguaje de bajo nivel para navegadores. Ofrece a los desarrolladores la capacidad de crear código que se compila en algo distinto de JavaScript. Esto permite que los programas incluidos en las páginas web funcionen casi tan rápido como las aplicaciones nativas para varias plataformas. Dichos programas se ejecutan en un entorno limitado y seguro.

Los representantes de los equipos responsables del desarrollo de todos los principales navegadores (Chrome, Firefox, Safari y Edge) participaron en la creación del estándar WebAssembly. Acordaron una arquitectura de sistema a principios de 2017. Ahora todos los navegadores anteriores admiten WebAssembly. De hecho, esta tecnología se puede utilizar en aproximadamente el 87% de los navegadores.

El código de WebAssembly existe en formato binario. Esto significa que dicho código es más pequeño que un código JavaScript similar y se carga más rápido. El código wasm, además, se puede presentar en formato de texto , para que las personas puedan leerlo y editarlo.

Cuando apareció por primera vez el estándar WebAssembly, algunos desarrolladores pensaron que bien podría ocupar el lugar de JavaScript y convertirse en el idioma principal de la web. Pero WebAssembly se ve mejor como una nueva herramienta que se integra bien con la plataforma web existente. Este es uno de sus objetivos prioritarios .

En lugar de reemplazar JavaScript donde este lenguaje se ha utilizado durante mucho tiempo y con éxito, WebAssembly ofrece nuevas oportunidades interesantes para los desarrolladores web. Es cierto que el código Wasm no tiene acceso directo al DOM, por lo que la mayoría de los proyectos web existentes continuarán usando JavaScript. Este lenguaje, a lo largo de los años de desarrollo y optimización, ya es bastante rápido. Y WebAssembly tiene sus propias aplicaciones :

  • Juegos
  • Cálculos científicos, visualizaciones, simulaciones.
  • Aplicaciones CAD.
  • Edición de imágenes y videos.

Todos estos usos para Wasm están unidos por lo que sus respectivas aplicaciones generalmente se consideran de escritorio. Pero debido al hecho de que WebAssembly le permite alcanzar un nivel de rendimiento cercano al nativo, muchas aplicaciones similares ahora se pueden implementar utilizando la plataforma web.

WebAssembly puede aprovechar los proyectos web existentes. Un ejemplo es el proyecto Figma . Gracias al uso de Wasm, el tiempo de carga de este proyecto mejoró significativamente. Si un sitio web usa código que realiza cálculos pesados, entonces, para mejorar el rendimiento de este sitio web, tiene sentido reemplazar solo dicho código con un análogo de WebAssembly.

Es posible que desee intentar utilizar WebAssembly en sus propios proyectos. Este idioma se puede aprender y escribir inmediatamente en él . Pero, sin embargo, WebAssembly se desarrolló originalmente como un objetivo de compilación para otros idiomas. Fue diseñado con buen soporte para C y C ++. Wasm experimental support apareció en Go 1.11. Se está poniendo mucho esfuerzo en escribir aplicaciones Wasm en Rust .

Pero es muy posible que los desarrolladores web no quieran aprender C, C ++, Go o Rust solo para usar WebAssembly. Que hacen La respuesta a esta pregunta puede dar AssemblyScript.

AssemblyScript


AssemblyScript es un compilador que convierte el código TypeScript en código WebAssembly. TypeScript es un lenguaje desarrollado por Microsoft. Este es un superconjunto de JavaScript, con soporte de tipo mejorado y algunas otras características. TypeScript se ha convertido en un lenguaje bastante popular . Cabe señalar que AssemblyScript puede convertir a Wasm solo un conjunto limitado de construcciones TypeScript. Esto significa que incluso alguien que no esté familiarizado con TypeScript puede aprender rápidamente este lenguaje a un nivel suficiente para escribir código que AssemblyScript entienda.

Además, dado que TypeScript es muy similar a JavaScript, podemos decir que la tecnología AssemblyScript permite a los desarrolladores web integrar fácilmente módulos Wasm en sus proyectos sin la necesidad de aprender un lenguaje completamente nuevo.

Ejemplo


Escribamos nuestro primer módulo AssemblyScript. Todo el código que vamos a discutir ahora se puede encontrar en GitHub . Para admitir WebAssembly, necesitamos al menos Node.js 8.

Cree un nuevo directorio, inicialice el proyecto npm e instale AssemblyScript:

mkdir assemblyscript-demo cd assemblyscript-demo npm init npm install --save-dev github:AssemblyScript/assemblyscript 

Tenga en cuenta que AssemblyScript debe instalarse directamente desde el repositorio GitHub del proyecto. AssemblyScript aún no se ha publicado en npm, ya que los desarrolladores aún no lo consideran listo para su uso generalizado.

Crearemos archivos auxiliares utilizando el comando asinit incluido en AssemblyScript:

 npx asinit . 

Ahora la sección de scripts de nuestro package.json debería tomar la siguiente forma:

 {  "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"  } } 

El archivo index.js ubicado en la carpeta raíz del proyecto se verá así:

 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 }); 

Esto le permite incluir módulos WebAssembly en su código utilizando el comando require . Es decir, de la misma manera que los módulos JavaScript normales están conectados.

La carpeta de assembly contiene el archivo index.ts . Tiene código fuente escrito de acuerdo con las reglas de AssemblyScript. El código repetitivo generado automáticamente es una función simple para agregar dos números:

 export function add(a: i32, b: i32): i32 {  return a + b; } 

Quizás esperaba que la firma de una función similar se pareciera a add(a: number, b: number): number . Por lo tanto, se vería si estuviera escrito en TypeScript ordinario. Pero aquí, en lugar del tipo de number , se i32 tipo i32 . Esto sucede porque el código AssemblyScript utiliza tipos específicos de WebAssembly para enteros y números de coma flotante, en lugar del tipo de número genérico de TypeScript.

Armemos el proyecto:

 npm run asbuild 

Los siguientes archivos deberían aparecer en la carpeta de build :

 optimized.wasm optimized.wasm.map optimized.wat untouched.wasm untouched.wasm.map untouched.wat 

Hay versiones optimizadas y regulares del ensamblaje. Cada versión del ensamblaje nos proporciona un archivo .wasm binario, un mapa de mapa .wasm.map y una representación textual del código binario en un archivo .wat. La presentación de prueba del código Wasm está destinada al programador, pero ni siquiera miraremos este archivo. En realidad, una de las razones para usar AssemblyScript es que elimina la necesidad de trabajar con código Wasm.

Ahora ejecutemos Node.js en modo REPL y asegurémonos de que el módulo Wasm compilado se pueda usar de la misma manera que cualquier módulo JS normal:

 $ node Welcome to Node.js v12.10.0. Type ".help" for more information. > const add = require('./index').add; undefined > add(3, 5) 8 

En general, esto es todo lo que se necesita para usar la tecnología WebAssembly en Node.js.

Equipar un proyecto con un guion de observador


Para reconstruir automáticamente el módulo durante el desarrollo al realizar cambios, recomiendo usar el paquete onchange . El hecho es que AssemblyScript aún no tiene su propio sistema para monitorear los cambios de archivos. Instale el paquete onchange:

 npm install --save-dev onchange 

Agregue asbuild:watch script a package.json . El indicador -i se incluye en el comando para que el proceso de compilación comience una vez cuando se llama al script, antes de que ocurra cualquier evento.

 {  "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"  } } 

Ahora, en lugar de ejecutar constantemente el comando asbuild , solo ejecuta asbuild:watch una vez.

Rendimiento


Escribiremos una prueba simple que evaluará el nivel de rendimiento del código Wasm. El alcance principal de WebAssembly es resolver tareas que usan el procesador de manera intensiva. Por ejemplo, estos son algún tipo de cálculos "pesados". Creemos una función que descubra si cierto número es primo.

La implementación básica de JS de una función similar se muestra a continuación. Está organizado de manera muy simple, verifica el número por fuerza bruta, pero para nuestros propósitos es adecuado, ya que realiza grandes cantidades 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; } 

Una función similar, escrita para el compilador AssemblyScript, se ve casi igual. La principal diferencia es la presencia de anotaciones de tipo en el 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 analizar el rendimiento del código, utilizaremos el paquete Benchmark.js . Instalarlo:

 npm install --save-dev benchmark 

Cree el archivo 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(); 

Esto es lo que logré obtener después de ejecutar el comando de node benchmark en mi computadora:

 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 puede ver, la implementación de AssemblyScript del algoritmo fue un 20% más rápida que la implementación de JS. Sin embargo, tenga en cuenta que esta prueba es un microbenchmark . No confíe demasiado en sus resultados.

Con el fin de encontrar resultados más confiables de la investigación de rendimiento de los proyectos AssemblyScript, recomiendo echar un vistazo a este y estos puntos de referencia.

Uso de un módulo Wasm en una página web


Usemos nuestro módulo Wasm en una página web. Comenzamos creando un archivo index.html con los siguientes contenidos:

 <!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> 

Ahora cree el archivo demo.js cuyo código se muestra a continuación. Hay muchas formas de cargar módulos WebAssembly. Lo más eficiente es compilarlos e inicializarlos en modo de transmisión mediante la función WebAssembly.instantiateStreaming . Tenga en cuenta que tendremos que redefinir la función de cancelación , que se llama si alguna instrucción no se ejecuta.

 (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.`;    }); })(); 

A continuación, instale el paquete del servidor estático . Necesitamos un servidor debido al hecho de que para usar la función WebAssembly.instantiateStreaming , el módulo debe ser reparado usando el tipo MIME de application/wasm .

 npm install --save-dev static-server 

Agregue el script apropiado a package.json :

 {  "scripts": {    "serve-demo": "static-server"  } } 

Ahora npm run serve-demo y abra la URL del host local en el navegador. Si ingresa un cierto número en el formulario, puede averiguar si es simple o no. Ahora, al desarrollar AssemblyScript, hemos recorrido un largo camino desde la escritura del código hasta su uso en Node.js y en una página web.

Resumen


WebAssembly, y por lo tanto AssemblyScript, no puede acelerar mágicamente ningún sitio. Pero Wasm nunca tuvo la tarea de acelerar absolutamente todo. Esta tecnología es notable porque abre el camino a la web para nuevos tipos de aplicaciones.

Algo similar se puede decir sobre AssemblyScript. Esta tecnología simplifica el acceso a WebAssembly para una gran cantidad de desarrolladores. Al crear código en un lenguaje cercano a JavaScript, permite usar las capacidades de WebAssembly para resolver problemas computacionales complejos.

Estimados lectores! ¿Cómo evalúa las perspectivas de utilizar AssemblyScript en sus proyectos?


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


All Articles