Arrays escritos: Datos binarios en el navegador

Ilmari Heikkinen

Introducción

Los arrays tipados son una incorporación relativamente reciente a los navegadores, que surgió de la necesidad de tener una forma eficiente de controlar los datos binarios en WebGL. Un array tipado es un bloque de memoria con una vista tipada, al igual que funcionan los arrays en C. Debido a que un array escrito está respaldado por memoria sin procesar, el motor de JavaScript puede pasar la memoria directamente a bibliotecas nativas sin tener que convertir los datos en una representación nativa laboriosamente. Como resultado, los arrays con tipos tienen un mejor rendimiento que los arrays de JavaScript para pasar datos a WebGL y otras APIs que trabajan con datos binarios.

Las vistas de arrays tipados actúan como arrays de un solo tipo para un segmento de un ArrayBuffer. Hay vistas para todos los tipos numéricos habituales, con nombres autodescriptivos, como Float32Array, Float64Array, Int32Array y Uint8Array. También hay una vista especial que reemplazó el tipo de array de píxeles en ImageData de Canvas: Uint8ClampedArray.

DataView es el segundo tipo de vista y está diseñado para controlar datos heterogéneos. En lugar de tener una API similar a un array, el objeto DataView te proporciona una API de get/set para leer y escribir tipos de datos arbitrarios en desplazamientos de bytes arbitrarios. DataView funciona muy bien para leer y escribir encabezados de archivos y otros datos similares a estructuras.

Conceptos básicos para usar arrays tipados

Vistas de arrays escritos

Para usar Arrays tipados, debes crear un ArrayBuffer y una vista para él. La forma más sencilla es crear una vista de array escrita del tamaño y tipo deseados.

// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];

Existen varios tipos diferentes de vistas de arrays escritos. Todas comparten la misma API, por lo que, una vez que sepas cómo usar una, sabrás cómo usarlas todas. En el siguiente ejemplo, crearé una de cada vista de array escrito que existe actualmente.

// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);

// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);

// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);

El último es un poco especial, ya que limita los valores de entrada entre 0 y 255. Esto es especialmente útil para los algoritmos de procesamiento de imágenes de Canvas, ya que ahora no tienes que restringir manualmente las matemáticas de procesamiento de imágenes para evitar que se desborde el rango de 8 bits.

Por ejemplo, así es como aplicarías un factor gamma a una imagen almacenada en un Uint8Array. No es muy atractivo:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

Con Uint8ClampedArray, puedes omitir la fijación manual:

pixels[i] *= gamma;

La otra forma de crear vistas de arrays tipados es crear primero un ArrayBuffer y, luego, crear vistas que apunten a él. Las APIs que obtienen datos externos suelen trabajar con ArrayBuffers, por lo que esta es la forma en que obtienes una vista de array tipada para ellos.

var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);

También puedes tener varias vistas del mismo ArrayBuffer.

var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.

Para copiar un array escrito a otro array escrito, la forma más rápida es usar el método de conjunto de array escrito. Para usar memcpy, crea Uint8Arrays en los búferes de las vistas y usa set para copiar los datos.

function memcpy(dst, dstOffset, src, srcOffset, length) {
  var dstU8 = new Uint8Array(dst, dstOffset, length);
  var srcU8 = new Uint8Array(src, srcOffset, length);
  dstU8.set(srcU8);
};

DataView

Para usar ArrayBuffers que contienen datos con tipos heterogéneos, la forma más sencilla es usar un DataView en el búfer. Supongamos que tenemos un formato de archivo que tiene un encabezado con un int sin firma de 8 bits seguido de dos de 16 bits y, luego, un array de carga útil de 32 bits de número de punto flotante. Es posible volver a leer esto con vistas de arrays escritas, pero es un poco complicado. Con un DataView, podemos leer el encabezado y usar una vista de array tipada para el array de números de punto flotante.

var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
  vectors[i] = dv.getFloat32(off);
}

En el ejemplo anterior, todos los valores que leo son big-endian. Si los valores del búfer son de formato little-endian, puedes pasar el parámetro opcional littleEndian al método get:

...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...

Ten en cuenta que las vistas de arrays tipados siempre están en el orden de bytes nativo. Esto es para que sean rápidas. Debes usar un DataView para leer y escribir datos en los que el orden de bytes será un problema.

DataView también tiene métodos para escribir valores en búferes. Estos set se nombran de la misma manera que los get, "set" seguido del tipo de datos.

dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5

Un debate sobre el endianismo

El orden de bytes, o bien orden de bytes, es el orden en el que se almacenan los números de varios bytes en la memoria de la computadora. El término big-endian describe una arquitectura de CPU que almacena el byte más significativo primero; little-endian, el byte menos significativo primero. El orden de bytes que se usa en una arquitectura de CPU determinada es completamente arbitrario; hay buenas razones para elegir cualquiera de ellos. De hecho, algunas CPUs se pueden configurar para admitir datos de formato big-endian y little-endian.

¿Por qué debes preocuparte por el orden de bytes? El motivo es simple. Cuando se leen o escriben datos desde el disco o la red, se debe especificar el orden de los bytes de los datos. Esto garantiza que los datos se interpreten correctamente, independientemente del orden de bytes de la CPU que trabaja con ellos. En un mundo cada vez más conectado en red, es esencial admitir adecuadamente todo tipo de dispositivos, grandes o pequeños, que podrían necesitar trabajar con datos binarios provenientes de servidores u otros pares en la red.

La interfaz de DataView está diseñada específicamente para leer y escribir datos desde y hacia archivos y la red. DataView opera en datos con un orden de bytes especificado. El orden de bytes, grande o pequeño, se debe especificar con cada acceso de cada valor, lo que garantiza que obtengas resultados coherentes y correctos cuando leas o escribas datos binarios, independientemente del orden de bytes de la CPU en la que se ejecuta el navegador.

Por lo general, cuando tu aplicación lee datos binarios de un servidor, debes analizarlos una vez para convertirlos en las estructuras de datos que tu aplicación usa de forma interna. DataView debe usarse durante esta fase. No es recomendable usar las vistas de array con tipo de varios bytes (Int16Array, Uint16Array, etc.) directamente con los datos recuperados a través de XMLHttpRequest, FileReader o cualquier otra API de entrada y salida, ya que las vistas de array con tipo especificado usan la endianidad nativa de la CPU. Explicaré eso después.

Veamos algunos ejemplos simples. El formato de archivo Windows BMP solía ser el formato estándar para almacenar imágenes en los primeros días de Windows. En la documentación vinculada anteriormente, se indica claramente que todos los valores enteros del archivo se almacenan en formato little-endian. Este es un fragmento de código que analiza el principio del encabezado BMP con la biblioteca DataStream.js que acompaña a este artículo:

function parseBMP(arrayBuffer) {
  var stream = new DataStream(arrayBuffer, 0,
    DataStream.LITTLE_ENDIAN);
  var header = stream.readUint8Array(2);
  var fileSize = stream.readUint32();
  // Skip the next two 16-bit integers
  stream.readUint16();
  stream.readUint16();
  var pixelOffset = stream.readUint32();
  // Now parse the DIB header
  var dibHeaderSize = stream.readUint32();
  var imageWidth = stream.readInt32();
  var imageHeight = stream.readInt32();
  // ...
}

Este es otro ejemplo, este de la demostración de renderización de alto rango dinámico en el proyecto de muestras de WebGL. Esta demostración descarga datos sin procesar de punto flotante de formato little-endian que representan texturas de alto rango dinámico y debe subirlos a WebGL. Este es el fragmento de código que interpreta correctamente los valores de punto flotante en todas las arquitecturas de CPU. Supongamos que la variable “arrayBuffer” es un ArrayBuffer que se acaba de descargar del servidor a través de XMLHttpRequest:

var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
  data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
  tempArray[jj] =
    data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
  gl.RGB, gl.FLOAT, tempArray);

La regla general es: cuando recibas datos binarios del servidor web, haz un pase con un DataView. Lee los valores numéricos individuales y almacénalos en alguna otra estructura de datos, ya sea un objeto JavaScript (para pequeñas cantidades de datos estructurados) o una vista de array escrito (para grandes bloques de datos). Esto garantizará que tu código funcione correctamente en todo tipo de CPU. También usa DataView para escribir datos en un archivo o en la red, y asegúrate de especificar correctamente el argumento littleEndian en los diversos métodos set para producir el formato de archivo que creas o usas.

Recuerda que todos los datos que pasan por la red tienen implícitamente un formato y un orden de bytes (al menos, para cualquier valor de varios bytes). Asegúrate de definir y documentar claramente el formato de todos los datos que envía tu aplicación a través de la red.

APIs del navegador que usan arrays tipados

Les daré una breve descripción general de las diferentes APIs de navegadores que actualmente usan arrays escritos. La selección actual incluye WebGL, Canvas, la API de Web Audio, XMLHttpRequests, WebSockets, Web Workers, la API de Media Source y las APIs de File. En la lista de APIs, puedes ver que los arrays tipados son adecuados para trabajos multimedia sensibles al rendimiento, así como para pasar datos de forma eficiente.

WebGL

El primer uso de los arrays tipados fue en WebGL, donde se usa para pasar datos de búfer y de imagen. Para establecer el contenido de un objeto de búfer de WebGL, usa la llamada gl.bufferData() con un array escrito.

var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);

Los arrays tipados también se usan para pasar datos de textura. Este es un ejemplo básico de cómo pasar contenido de textura con un array tipado.

var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
  gl.TEXTURE_2D, // target
  0, // mip level
  gl.RGBA, // internal format
  16, 16, // width and height
  0, // border
  gl.RGBA, //format
  gl.UNSIGNED_BYTE, // type
  pixels // texture data
);

También necesitas arrays con tipos para leer píxeles del contexto de WebGL.

var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Lienzo 2D

Recientemente, se creó el objeto ImageData de Canvas para trabajar con la especificación de arrays escritos. Ahora puedes obtener una representación de arrays escritos de los píxeles en un elemento de lienzo. Esto es útil, ya que ahora también puedes crear y editar arreglos de píxeles de lienzo sin tener que manipular el elemento de lienzo.

var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray

XMLHttpRequest2

XMLHttpRequest recibió un aumento de array tipado y ahora puedes recibir una respuesta de array tipado en lugar de tener que analizar una cadena de JavaScript en un array tipado. Esto es muy útil para pasar los datos recuperados directamente a las APIs multimedia y analizar archivos binarios recuperados de la red.

Todo lo que tienes que hacer es establecer el responseType del objeto XMLHttpRequest en "arraybuffer".

xhr.responseType = 'arraybuffer';

Recuerda que debes estar al tanto de los problemas de endian cuando descargues datos de la red. Consulta la sección sobre el orden de bytes anterior.

APIs de archivos

FileReader puede leer el contenido de un archivo como un ArrayBuffer. Luego, puedes adjuntar vistas de arrays escritas y DataViews al búfer para manipular su contenido.

reader.readAsArrayBuffer(file);

También debes tener en cuenta el orden de los bytes. Consulta la sección de orden de bytes para obtener más información.

Objetos transferibles

Los objetos transferibles en postMessage permiten pasar datos binarios a otras ventanas y trabajadores web mucho más rápido. Cuando envías un objeto a un trabajador como transferible, el objeto se vuelve inaccesible en el subproceso de envío y el trabajador receptor obtiene la propiedad del objeto. Esto permite una implementación altamente optimizada en la que los datos enviados no se copian, solo se transfiere al receptor la propiedad del array escrito.

Para usar objetos transferibles con Web Workers, debes usar el método webkitPostMessage en el trabajador. El método webkitPostMessage funciona igual que postMessage, pero toma dos argumentos en lugar de uno. El segundo argumento agregado es un array de objetos que deseas transferir al trabajador.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Para recuperar los objetos del trabajador, este puede pasarlos al subproceso principal de la misma manera.

webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);

Cero copias, ¡genial!

API de Media Source

Recientemente, los elementos multimedia también obtuvieron algunas funciones de array tipado en forma de la API de Media Source. Puedes pasar directamente un array escrito que contenga datos de video a un elemento de video con webkitSourceAppend. De esta manera, el elemento de video adjunta los datos de video después del video existente. SourceAppend es excelente para crear anuncios intersticiales, playlists, transmisiones y otros usos en los que quieras reproducir varios videos con un solo elemento de video.

video.webkitSourceAppend(uint8Array);

WebSockets binarios

También puedes usar Arrays tipados con WebSockets para evitar tener que convertir todos tus datos en cadenas. Es ideal para escribir protocolos eficientes y minimizar el tráfico de red.

socket.binaryType = 'arraybuffer';

¡Uf! Con esto concluye la revisión de la API. Pasemos a ver las bibliotecas de terceros para controlar los arrays tipados.

Bibliotecas de terceros

jDataView

jDataView implementa un shim de DataView para todos los navegadores. DataView solía ser una función exclusiva de WebKit, pero ahora es compatible con la mayoría de los otros navegadores. El equipo de desarrolladores de Mozilla está en proceso de lanzar un parche para habilitar DataView en Firefox.

Eric Bidelman, del equipo de Relaciones con Desarrolladores de Chrome, escribió un pequeño ejemplo de lector de etiquetas ID3 de MP3 que usa jDataView. Este es un ejemplo de uso de la entrada de blog:

var dv = new jDataView(arraybuffer);

// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
  var title = dv.getString(30, dv.tell());
  var artist = dv.getString(30, dv.tell());
  var album = dv.getString(30, dv.tell());
  var year = dv.getString(4, dv.tell());
} else {
  // no ID3v1 data found.
}

codificación de cadenas

Trabajar con cadenas en arrays tipados es un poco complicado en este momento, pero existe la biblioteca de codificación de cadenas que ayuda en este caso. Stringencoding implementa la especificación de codificación de cadenas de arrays tipados propuesta, por lo que también es una buena manera de tener una idea de lo que viene.

Este es un ejemplo de uso básico de la codificación en cadena:

var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

BitView.js

Escribí una pequeña biblioteca de manipulación de bits para arrays tipados llamada BitView.js. Como su nombre lo indica, funciona de manera similar a DataView, excepto que funciona con bits. Con BitView, puedes obtener y establecer el valor de un bit en una compensación de bits determinada en un ArrayBuffer. BitView también tiene métodos para almacenar y cargar enteros de 6 y 12 bits en offsets de bits arbitrarios.

Los números enteros de 12 bits son útiles para trabajar con coordenadas de pantalla, ya que las pantallas suelen tener menos de 4,096 píxeles en la dimensión más larga. Si usas números enteros de 12 bits en lugar de números enteros de 32 bits, obtienes una reducción de tamaño del 62%. Para un ejemplo más extremo, estaba trabajando con archivos Shapefile que usan números de punto flotante de 64 bits para las coordenadas, pero no necesitaba la precisión porque el modelo solo se iba a mostrar en el tamaño de la pantalla. El cambio a coordenadas de base de 12 bits con deltas de 6 bits para codificar los cambios de la coordenada anterior redujo el tamaño del archivo a una décima parte. Puedes ver la demostración aquí.

El siguiente es un ejemplo del uso de BitView.js:

var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.

bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.

bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.

DataStream.js

Una de las características más interesantes de los arrays tipados es la forma en que facilitan el manejo de archivos binarios en JavaScript. En lugar de analizar un carácter de cadena por carácter y convertirlos manualmente en números binarios, ahora puedes obtener un ArrayBuffer con XMLHttpRequest y procesarlo directamente con un objeto DataView. Esto facilita, por ejemplo, cargar un archivo MP3 y leer las etiquetas de metadatos para usarlas en tu reproductor de audio. También puedes cargar un archivo Shapefile y convertirlo en un modelo WebGL. También puedes leer las etiquetas EXIF de un archivo JPEG y mostrarlas en tu app de presentación de diapositivas.

El problema con los XHR de ArrayBuffer es que leer datos similares a struct desde el búfer es un poco complicado. DataView es bueno para leer algunos números a la vez de forma segura en endian. Las vistas de array escrito son buenas para leer arrays de números endian nativos alineados con el tamaño de los elementos. Lo que nos faltaba era una forma de leer arrays y estructuras de datos de una manera conveniente y segura para el orden de bytes. Ingresa DataStream.js.

DataStream.js es una biblioteca de arrays escritos que lee y escribe escalares, cadenas, arrays y structs de datos de ArrayBuffers con el formato de un archivo.

Ejemplo de lectura en un array de números de punto flotante desde un ArrayBuffer:

// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
  f32[i] = dv.getFloat32(i*4, littleEndian);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);

DataStream.js se vuelve realmente útil en la lectura de datos más complejos. Supongamos que tienes un método que lee marcadores JPEG:

// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
  var obj = {};
  obj.tag = dv.getUint16(i);
  i += 2;
  obj.length = dv.getUint16(i);
  i += 2;
  obj.data = new Uint8Array(obj.length - 2);
  for (var j=0; j<obj.data.length; j++,i++) {
    obj.data[j] = dv.getUint8(i);
  }
  objs.push(obj);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
  var obj = {};
  obj.tag = ds.readUint16();
  obj.length = ds.readUint16();
  obj.data = ds.readUint8Array(obj.length - 2);
  objs.push(obj);
}

También puedes usar el método DataStream.readStruct para leer estructuras de datos. El método readStruct toma un array de definición de struct que contiene los tipos de los miembros de los structs. Tiene funciones de devolución de llamada para controlar tipos complejos y también maneja arrays de datos y estructuras anidadas:

// with DataStream.readStruct
ds.readStruct([
  'objs', ['[]', [ // objs: array of tag,length,data structs
    'tag', 'uint16',
    'length', 'uint16',
    'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
  '*'] // read in as many struct as there are
]);

Como puedes ver, la definición de la struct es un array plano de pares [name, type]. Las estructuras anidadas se crean con un array para el tipo. Los arrays se definen con un array de tres elementos, en el que el segundo elemento es el tipo de elemento del array y el tercer elemento es la longitud del array (ya sea como un número, como una referencia al campo leído anteriormente o como una función de devolución de llamada). El primer elemento de la definición del array no se usa.

Los valores posibles para el tipo son los siguientes:

Number types

Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.

  'uint8' -- 8-bit unsigned int
  'uint16' -- 16-bit unsigned int
  'uint32' -- 32-bit unsigned int
  'int8' -- 8-bit int
  'int16' -- 16-bit int
  'int32' -- 32-bit int
  'float32' -- 32-bit float
  'float64' -- 64-bit float

String types

  'cstring' -- ASCII string terminated by a zero byte.
  'string:N' -- ASCII string of length N.
  'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
  'u16string:N' -- UCS-2 string of length N in DataStream endianness.
  'u16stringle:N' -- UCS-2 string of length N in little-endian.
  'u16stringbe:N' -- UCS-2 string of length N in big-endian.

Complex types

  [name, type, name_2, type_2, ..., name_N, type_N] -- Struct

  function(dataStream, struct) {} -- Callback function to read and return data.

  {get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
  -- Getter/setter functions to reading and writing data. Handy for using the
     same struct definition for both reading and writing.

  ['', type, length] -- Array of given type and length. The length can be either
                        a number, a string that references a previously-read
                        field, or a callback function(struct, dataStream, type){}.
                        If length is set to '*', elements are read from the
                        DataStream until a read fails.

Puedes ver un ejemplo en vivo de cómo leer metadatos JPEG aquí. La demostración usa DataStream.js para leer la estructura a nivel de la etiqueta del archivo JPEG (junto con algunos análisis de EXIF) y jpg.js para decodificar y mostrar la imagen JPEG en JavaScript.

Historia de los arrays escritos

Los arrays tipados comenzaron en la etapa inicial de implementación de WebGL, cuando descubrimos que pasar arrays de JavaScript al controlador de gráficos causaba problemas de rendimiento. Con los arrays de JavaScript, la vinculación de WebGL debía asignar un array nativo y llenarlo con un recorrido por el array de JavaScript y transmitir cada objeto de JavaScript del array al tipo nativo requerido.

Para solucionar el cuello de botella de conversión de datos, Vladimir Vukicevic de Mozilla escribió CanvasFloatArray: un array de números de punto flotante de estilo C con una interfaz de JavaScript. Ahora puedes editar el CanvasFloatArray en JavaScript y pasarlo directamente a WebGL sin tener que realizar ningún trabajo adicional en la vinculación. En otras iteraciones, se cambió el nombre de CanvasFloatArray a WebGLFloatArray, que ahora se llama Float32Array y se dividió en un ArrayBuffer de respaldo y el tipo de vista Float32Array-view para acceder al búfer. También se agregaron tipos para otros tamaños de números enteros y de punto flotante, y variantes con firma y sin ella.

Consideraciones del diseño

Desde el comienzo, el diseño de los arrays escritos se centró en la necesidad de pasar datos binarios a bibliotecas nativas de forma eficiente. Por esta razón, las vistas de array escrito operan sobre datos alineados en el endianismo nativo de la CPU del host. Estas decisiones permiten que JavaScript alcance el máximo rendimiento durante operaciones como el envío de datos de vértices a la tarjeta gráfica.

DataView está diseñado específicamente para la E/S de archivos y redes, en la que los datos siempre tienen un orden de bytes especificado y es posible que no estén alineados para obtener el máximo rendimiento.

La división de diseño entre el ensamblaje de datos en la memoria (mediante vistas de array escrito) y la E/S (con DataView) fue consciente. Los motores de JavaScript modernos optimizan en gran medida las vistas de array escritas y logran un alto rendimiento en las operaciones numéricas con ellas. Esta decisión de diseño permitió alcanzar los niveles actuales de rendimiento de las vistas de arrays escritos.

Referencias