Uploaded avatar of iHiD

¡Es noviembre de nibbles!

@iHiD
hace Casi 3 años
Vídeo

Introducción

Hola a todos. Bienvenidos a noviembre. Espero que os vaya bien.

Hemos tenido un octubre muy ajetreado. Acabamos de lanzar una gran mejora en las soluciones de la comunidad. Ahora las deduplicamos, de modo que las soluciones similares solo aparecen una vez; hemos añadido nuevas opciones de ordenación, la posibilidad de buscar por código y, si echas un vistazo a C#, verás que también hemos añadido la posibilidad de filtrar por distintos conceptos de programación. Así puedes buscar soluciones que usen desplazamiento de bits, recursión o cualquier otra cosa que te apetezca. Vamos a extenderlo a otros tracks durante la semana de la comunidad.

Pero por ahora, centrémonos en #12in23. Octubre fue un mes interesante, explorando la programación orientada a objetos, pero este mes vamos a por lo más duro. Nos centramos en los lenguajes ensamblador y, en concreto, en el ensamblador de MIPS, el de x86-64 y WebAssembly. Como siempre, Erik nos explicará qué hace que estos lenguajes sean interesantes y únicos.

Las insignias

Como siempre, puedes ganar la insignia Nibbly November completando 5 ejercicios cualesquiera en estos lenguajes. También tenemos la insignia de todo el año, y sé que muchos de vosotros vais a por ella. Para eso, os proponemos 5 ejercicios destacados que completar. Son los siguientes:

  • Pop Count: cuenta los bits a 1 de un número
  • Grains: calcula el número de granos en un tablero de ajedrez con casillas que se van duplicando
  • Resistor Color: convierte el color de una banda de resistencia en su representación numérica
  • Rotational Cipher: implementa el cifrado de rotación (también conocido como cifrado César)
  • Nucleotide Count: calcula cuántas veces aparece cada nucleótido en un string de ADN

Contexto

¿Por qué llamarlo «Nibbly November»?

Bueno, como seguramente ya sepas, un byte son 8 bits. Y un nibble son 4 bits. El nombre empieza a tener sentido si cambias la «y» de byte por una «i», lo que te da bite. Un nibble es, entonces, un pequeño bocado.

¿Qué es un lenguaje ensamblador?

Vamos a empezar por lo básico. Una CPU ejecuta instrucciones, como «sumar dos números» o «desplazar bits a la izquierda», etc. Estas instrucciones se conocen como instrucciones de código máquina y no son más que secuencias concretas de bits. Ejecutar un programa es, «simplemente» (entre comillas), que la CPU procese y ejecute esas secuencias de bits.

Como escribir directamente las instrucciones como secuencias de bits es engorroso y propenso a errores, Kathleen y Andrew Donald Booth idearon un lenguaje más amable para las personas con el que representar las instrucciones de código máquina allá por 1947. Un lenguaje así, que representa instrucciones de código máquina, se llama lenguaje ensamblador. Ese lenguaje ensamblador se convierte después en instrucciones de código máquina mediante un «ensamblador».

Un aspecto interesante de los lenguajes ensamblador es que están directamente ligados a una arquitectura de CPU, aunque normalmente son independientes del sistema operativo.

Por cierto, si te acuerdas de las tarjetas perforadas, esos grandes trozos de cartón que se usaban en los primeros ordenadores para hacerles ejecutar programas, ¡también eran un lenguaje ensamblador!

¿En qué se diferencia el lenguaje ensamblador de los lenguajes con los que programamos ahora?

La diferencia clave es que los lenguajes ensamblador son de muy bajo nivel. Echarás de menos muchas de las abstracciones a las que te has acostumbrado. Las clases y los objetos no aparecen por ninguna parte. ¿Bucles? Tendrás que escribirlos a mano mediante saltos. ¿Funciones? ¡Ni hablar! Escribir código ensamblador fue toda una lección de humildad para mí, porque te das cuenta de cuánto te facilitan la vida los lenguajes modernos. Pero también es increíblemente útil escribir código ensamblador, porque entenderás mucho mejor cómo funcionan realmente las cosas.

Dato curioso: ¡el 99 % del código fuente de RollerCoaster Tycoon estaba escrito a mano en ensamblador! Es un logro asombroso, que valorarás aún más cuando hayáis escrito un poco de ensamblador vosotros mismos.

¿Se sigue escribiendo código ensamblador?

Bueno, menos que antes. Antaño solía ocurrir que el ensamblador escrito a mano superaba en rendimiento al código máquina generado por el compilador (una de las razones por las que C++ permite incrustar código ensamblador directamente), pero los compiladores se han vuelto tan buenos generando código máquina que esto ya rara vez es cierto. Dicho esto, seguirás encontrando lenguaje ensamblador en entornos donde el rendimiento es crítico o los recursos son limitados.

Resúmenes

MIPS

  • MIPS (Microprocessor without Interlocked Pipelined Stages) es una familia de arquitecturas de conjunto de instrucciones de tipo RISC (reduced instruction set computer, computador con conjunto de instrucciones reducido)
  • Desarrollada por MIPS Computer Systems y publicada por primera vez en 1985
  • Varias versiones: MIPS I, II, III, IV, V y MIPS32/64. Las dos primeras versiones eran solo de 32 bits, pero MIPS III introdujo la compatibilidad con 64 bits.
  • Varias extensiones opcionales, como instrucciones SIMD y compresión
  • Muy influyente en las arquitecturas RISC posteriores
  • En 2021, MIPS anunció que la arquitectura MIPS ya no se seguía desarrollando y que habían migrado a RISC-V (una arquitectura de código abierto y libre de regalías)
  • Se usa sobre todo en sistemas empotrados (por ejemplo, enrutadores) y servidores (la usaban los ordenadores de Silicon Graphics, famosos por los efectos especiales de las películas), el superordenador NEC Cenju-4, el Tesla Model S, la sonda New Horizons de la NASA, pero también para enseñar ensamblador en universidades y en varias consolas (por ejemplo, la PlayStation original, la PlayStation Portable y la Nintendo 64)

Ensamblador de x86-64

  • Diseñado por AMD y publicado en 1999 como la arquitectura AMD64
  • Es una versión de 64 bits del conjunto de instrucciones x86, cuyos orígenes se remontan a 1978, cuando Intel lanzó su microprocesador 8086. Aquel era un procesador de 16 bits, pero más adelante el 80386 añadió instrucciones de 32 bits, y ese conjunto de instrucciones se ha convertido en sinónimo de x86.
  • Lo principal que permitieron los 64 bits fue direccionar más memoria (el direccionamiento de 32 bits está limitado a 4 GB), que se había convertido en un cuello de botella. En teoría, los 64 bits pueden direccionar 16 exabytes, pero actualmente solo se usan 48 bits, lo que permite direccionar 256 TB (se puede ampliar más adelante cuando haga falta)
  • AMD64 amplía el conjunto de instrucciones x86 y se diseñó para ser totalmente compatible con las aplicaciones de 16 y 32 bits existentes mediante un modo de compatibilidad
    • Intel había diseñado la IA-64 sin la participación de AMD. Era un conjunto de instrucciones de 64 bits nuevo, muy distinto y no retrocompatible. Al final se impuso AMD64, e Intel implementó su propia versión (con solo pequeñas diferencias semánticas)
  • Se usa en todas partes. Desde estaciones de trabajo hasta servidores (incluidos superordenadores), desde sistemas empotrados hasta consolas (por ejemplo, la PS5 y la Xbox Series X).

WebAssembly

  • Diseñado por el W3C, un organismo de normalización de tecnologías web
  • Sus objetivos de diseño son:
    • Rápido, seguro y portátil
    • Una representación eficiente y portátil
  • Antaño, la ejecución rápida en la web se conseguía normalmente mediante plugins especializados del navegador, como Flash y Silverlight, ya que JavaScript no se presta demasiado bien a la computación de alto rendimiento. Los principales inconvenientes de estos plugins eran que solían tener muchos problemas de seguridad y no estaban estandarizados.
    • Mozilla diseñó asm.js, un subconjunto de JavaScript cuyo objetivo era permitir ejecutar código en el navegador con un rendimiento excelente, algo que lograba mediante la consistencia de tipos (sin cambios dinámicos de tipo) y sin recolección de basura. Los lenguajes podían entonces compilarse a asm.js y aun así obtener buen rendimiento al ejecutarse en la web. Pero seguía siendo JS, así que estaba limitado en lo que se podía conseguir. De ahí la propuesta de un nuevo lenguaje: WASM.
  • Es un lenguaje de tipo ensamblador, en el sentido de que proporciona un conjunto de instrucciones que se ejecutan. Algo crucial: no está ligado a una CPU concreta, por lo que es independiente de la plataforma y necesita una implementación para cada plataforma (una máquina virtual). Esto significa que WebAssembly es, en realidad, bytecode, no código máquina
  • Con tipado estático (una diferencia crucial respecto a JS)
  • Normalmente usa compilación anticipada (ahead-of-time) o justo a tiempo (just-in-time), aunque también puede interpretarse
  • Es un estándar abierto y define dos cosas:
    • Un formato binario
    • Un formato de texto (que se compila al formato binario)
  • Tiene implementaciones en todos los navegadores principales
  • Se usa en muchas páginas web que requieren un alto rendimiento, como Google Earth, Figma, Unity o Autocad. También está ganando terreno en el lado del servidor, por ejemplo para ejecutar microservicios, en plataformas SaaS (por ejemplo, CloudFlare workers) o en Docker

¿Y en qué se diferencian desde el punto de vista de la programación?

MIPS

  • Usa una arquitectura de carga-almacenamiento (también conocida como registro-registro), en la que las instrucciones o bien acceden a memoria o bien hacen aritmética, pero siempre operan exclusivamente con datos que están en registros

Ensamblador de x86-64

  • Usa una arquitectura registro-memoria, que permite realizar operaciones sobre (o desde) la memoria, además de sobre los registros

WebAssembly

  • Usa programación basada en pila (sin registros), con la opción de leer datos desde la memoria y escribirlos en ella

¿Qué hace grandes a estos lenguajes?

MIPS

  • Es pequeño. El conjunto de instrucciones de MIPS cabe entero en una sola página
  • Sus convenciones de llamada bien establecidas ayudan a saber cómo usar los registros disponibles, por ejemplo cuáles usar para pasar argumentos y cuáles para devolver resultados.
  • Es estable. La última versión se publicó en 2014
  • Está ampliamente documentado, sobre todo en textos académicos
  • Tiene muchísimo uso en el mundo real. Miles de millones de dispositivos

Ensamblador de x86-64

  • Aunque es una extensión de x64, se añadieron muchas características nuevas, entre ellas:
    • Compatibilidad con enteros de 64 bits
    • Registros adicionales
    • Instrucciones SSE (instrucciones vectoriales)
    • Acceso relativo a datos (más eficiente cuando se usan bibliotecas compartidas)
    • Bit de no ejecución (una característica de seguridad que impide ejecutar código en ciertas páginas de memoria)
  • Resulta familiar. Como amplía el conjunto de instrucciones x86, será relativamente fácil de aprender para quien ya conozca dicho conjunto de instrucciones Documentación extensa y detallada
  • Es estable. Aunque se añaden versiones nuevas con regularidad, el núcleo sigue siendo extremadamente estable y compatible con versiones anteriores

WebAssembly

  • La máquina virtual de WebAssembly, basada en pila, es ligera y sencilla en comparación con los lenguajes ensamblador de los procesadores físicos (incluidos los RISC). Eso hace que sea relativamente fácil compilar para ella.
  • El formato de texto de WebAssembly usa el «azúcar» de las S-expressions para conseguir un estilo imperativo conocido, que luego se transforma en código basado en pila. La forma con S-expressions se llama «forma azucarada» (sugared form) y se «desazucara» a la otra forma, equivalente a lo que hay en el binario. Las S-expressions les resultarán familiares a quienes hayan trabajado alguna vez con un LISP
  • Tiene una gran interoperabilidad con JavaScript. Es sencillo pasar datos desde y hacia JavaScript. Una advertencia importante: WASM todavía no permite interactuar con el DOM
  • Se mejora continuamente. No solo se mejoran las máquinas virtuales de WASM: el propio estándar también está en desarrollo activo. Se están diseñando y trabajando muchísimas características nuevas, como instrucciones relacionadas con SIMD, recolección de basura, hilos, optimizaciones de llamadas de cola y mucho más
  • Es seguro. El código se valida y se ejecuta en un entorno aislado, lo que ofrece un mayor grado de validación estática en comparación con JavaScript o los lenguajes ensamblador nativos. Su semántica está bien definida, lo que facilita comprobarla y razonar sobre ella

Características destacadas

MIPS

  • Eficiencia. Los procesadores MIPS son muy eficientes, lo que los hace ideales para sistemas empotrados.
  • Rendimiento. Tienen un gran rendimiento, de ahí que MIPS se haya usado en superordenadores
  • Fáciles de aprender. Con pocas instrucciones y cada una haciendo solo una cosa sencilla, resultan fáciles de aprender. Son ideales para la docencia.

Ensamblador de x86-64

  • Potente. x86-64 se ha ido afinando durante décadas, con muchísimas instrucciones que ayudan con el rendimiento. Un ejemplo son las SIMD (Single Instruction, Multiple Data), instrucciones que, bueno, permiten que una sola instrucción se ejecute en paralelo sobre múltiples datos.
  • Omnipresente. Los dispositivos que ejecutan x86-64 están por todas partes. Las CPU de Intel y AMD lo implementan. Ha sido el estándar de facto durante bastante tiempo
  • Se actualiza con regularidad. Por ejemplo, con nuevas instrucciones vectoriales, como SSE3-5, AVX, AVX-512 y más

WebAssembly

  • Eficiente. El formato binario es compacto y se puede decodificar, validar y compilar en una única pasada rápida. También permite el streaming, lo que hace que la decodificación, la validación y la compilación puedan empezar cuanto antes, incluso antes de haber visto todos los datos. Y también se puede paralelizar. Esto lo hace perfecto para aplicaciones web de alto rendimiento.
  • Un gran objetivo de compilación. Permite que código escrito en muchos lenguajes se ejecute en la web. La mayoría de los lenguajes principales admiten compilar a binarios de WebAssembly, lo que significa que tu código puede ejecutarse en la web sin necesidad de escribir JavaScript. Algunos lenguajes no compilan tu código a WebAssembly, sino que compilan el entorno de ejecución a WebAssembly, que después puede ejecutar bytecode sin modificarlo.
  • Es fácil de desplegar y ejecutar. Solo necesitas una máquina virtual capaz de interpretar el bytecode, y todos los navegadores principales incluyen una.
  • No está atado a la web, sino que también puede ejecutarse en el lado del servidor. WebAssembly System Interface (WASI) es una interfaz (ABI y API) diseñada para ser portátil a cualquier plataforma. Es similar a POSIX (interfaces estándar para sistemas Unix) y proporciona cosas como E/S. La seguridad es una parte clave de su diseño, que incluye el aislamiento y estar orientada a capacidades (tienes que pedir permisos explícitamente para cosas como ficheros o sockets). WASI también tiene el potencial de permitir una fácil comunicación entre lenguajes. Solomon Hykes, cofundador de Docker, escribió en 2019: «Si WASM+WASI hubiera existido en 2008, no habríamos necesitado crear Docker»

Cuál elegir

  • Si nunca has trabajado con un lenguaje ensamblador, WebAssembly es probablemente el más fácil para empezar. Dicho esto, si quieres aprender un lenguaje ensamblador que se compile a código máquina, prueba con el ensamblador de MIPS
  • Si trabajas en una máquina x86-64 (muy probable), prueba el ensamblador de x86-64
  • Si conoces LISP, apreciarás que WebAssembly use S-expressions
  • Si trabajas con aplicaciones web, WebAssembly es la opción más lógica
  • Si te importa el rendimiento, x86-64 y MIPS son grandes opciones. O, si lo que te importa es el rendimiento en la web, prueba WebAssembly
Translation missing: es-ES.number.nth.ordinalized Nov 2023 · ¿Te ha resultado útil?