Arrays escritos: Datos binarios en el navegador

Ilmari Heikkinen

Introducción

Los arrays escritos son una adición relativamente reciente a los navegadores, surgida de la necesidad de tener una forma eficiente de manejar datos binarios en WebGL. Un array escrito es una losa de memoria con una vista escrita en él, de manera similar a cómo 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 minuciosamente los datos en una representación nativa. Como resultado, los arrays escritos funcionan mucho mejor que los arrays de JavaScript para pasar datos a WebGL y otras APIs que trabajan con datos binarios.

Las vistas de array escrito actúan como arrays de un solo tipo en 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 manejar datos heterogéneos. En lugar de tener una API similar a un array, el objeto DataView te proporciona una API 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 struct.

Conceptos básicos para usar arrays escritos

Vistas de array escritos

Para usar arrays escritos, debes crear un ArrayBuffer y una vista de este. 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 array escritas. Todos comparten la misma API, de manera que, una vez que sabes cómo usar una, ya sabes cómo usar todas. En el siguiente ejemplo, crearé una de cada vista de array escrito existente.

// 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 restringe los valores de entrada entre 0 y 255. Esto es muy útil para los algoritmos de procesamiento de imágenes de Canvas, ya que ahora no tienes que restringir manualmente los cálculos de procesamiento de imágenes para evitar desbordar el rango de 8 bits.

Por ejemplo, a continuación, se muestra cómo aplicar un factor gamma a una imagen almacenada en un elemento Uint8Array. No muy bonito:

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

Con Uint8ClampedArray, puedes omitir las restricciones manuales:

pixels[i] *= gamma;

La otra forma de crear vistas de array escrito es crear primero un ArrayBuffer y, luego, crear vistas que apunten a él. Por lo general, las APIs que obtienen datos externos trabajan con ArrayBuffers, por lo que esta es la forma de obtener una vista de array escrito 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 al 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 en otro, la forma más rápida es usar el método de conjunto de arrays escritos. Para un uso similar a 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 de 8 bits sin firma seguido de dos ints de 16 bits, seguido de un array de carga útil de números de punto flotante de 32 bits. Leer esto con vistas de array escrito es factible, pero un poco doloroso. Con una DataView, podemos leer el encabezado y usar una vista de array escrito 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 bit-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 array escrito siempre están en el orden de bytes nativo. Esto es para que sean rápidos. Debes usar DataView para leer y escribir datos en los casos en que el formato sea un problema.

DataView también tiene métodos para escribir valores en búferes. Estos métodos set se nombran de la misma forma que los métodos 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 formato endian

El endianismo, u 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 importante primero; little-endian, el byte menos importante primero. El formato endian que se usa en una arquitectura de CPU determinada es completamente arbitrario; hay buenas razones para elegir una de ellas. De hecho, algunas CPU pueden configurarse para admitir datos big-endian y Little-endian.

¿Por qué hay que preocuparse por el formato endian? El motivo es simple. Cuando se realizan operaciones de lectura o escritura de datos desde el disco o la red, se debe especificar su formato endian. Esto garantiza que los datos se interpreten correctamente, independientemente del formato endian de la CPU que trabaja con ellos. En un mundo que está cada vez más conectado a la red, es fundamental admitir de forma adecuada todo tipo de dispositivos, big-and-small-endian, que quizás necesiten trabajar con datos binarios procedentes de servidores u otros pares de la red.

La interfaz DataView está diseñada específicamente para leer y escribir datos hacia y desde archivos y la red. DataView opera sobre datos con un endianismo especificado. El formato endian, grande o pequeño, debe especificarse con cada acceso a cada valor, lo que garantiza que obtengas resultados coherentes y correctos cuando leas o escribas datos binarios, sin importar el formato endian de la CPU en la que se ejecute 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 internamente. Se debe usar DataView durante esta fase. No es una buena idea usar las vistas de array de tipo multibyte (Int16Array, Uint16Array, etc.) directamente con los datos recuperados a través de XMLHttpRequest, FileReader o cualquier otra API de entrada/salida, ya que las vistas de array escrito usan el endianismo nativo de la CPU. Explicaré eso después.

Veamos un par de ejemplos sencillos. El formato de archivo Windows BMP solía ser el formato estándar para almacenar imágenes en los primeros días de Windows. La documentación vinculada anteriormente indica claramente que todos los valores enteros en el archivo se almacenan en formato Little-endian. Este es un fragmento de código que analiza el comienzo del encabezado BMP con la biblioteca DataStream.js que acompañ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 de la demostración de renderización de alto rango dinámico en el proyecto de ejemplos de WebGL. Esta demostración descarga datos de punto flotante de Little-endian sin procesar 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 la siguiente: después de recibir datos binarios del servidor web, realiza un pase sobre ellos con una 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. Además, usa DataView para escribir datos en un archivo o la red, y asegúrate de especificar correctamente el argumento littleEndian en los distintos métodos set para producir el formato de archivo que estás creando o usando.

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

APIs del navegador que usan arrays escritos

Voy a darte una descripción general breve de las diferentes APIs de navegador que actualmente usan arrays escritos. El recorte 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 escritos son adecuados para trabajos multimedia sensibles al rendimiento y para pasar datos de manera eficiente.

WebGL

Los arrays escritos se usaron por primera vez en WebGL, donde se usan para pasar datos del búfer y de imágenes. Para configurar 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 escritos también se usan para pasar datos de texturas. A continuación, se muestra un ejemplo básico para pasar contenido de textura con un array escrito.

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 tipográficos 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 lienzo para que funcione con la especificación de arrays Typed. Ahora puedes obtener una representación de estos arrays de los píxeles de un elemento de lienzo. Esto es útil, ya que ahora también puedes crear y editar arrays de píxeles de lienzo sin tener que manipular el elemento lienzo.

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

XMLHttpRequest2

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

Lo único que debes hacer es configurar el responseType del objeto XMLHttpRequest como "arraybuffer".

xhr.responseType = 'arraybuffer';

Recuerda que debes estar al tanto de los problemas de endianidad cuando descargues datos de la red. Consulta la sección anterior sobre endianismo.

APIs de archivos

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

reader.readAsArrayBuffer(file);

Aquí también debes tener en cuenta el formato endian. Consulta la sección de endianidad para obtener más detalles.

Objetos transferibles

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

Para usar objetos transferibles con trabajadores web, debes usar el método webkitPostMessage en el trabajador. El método webkitPostMessage funciona igual que postMessage, pero usa dos argumentos en lugar de solo 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 de vuelta al subproceso principal de la misma manera.

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

¡No tienes copias, no te preocupes!

API de fuente de medios

Recientemente, los elementos multimedia también lograron algo de beneficio de array escrito en la forma de la API de fuente de medios. Puedes pasar directamente un array escrito que contenga datos de video a un elemento de video usando webkitSourceAttach. De esta manera, el elemento de video agregará los datos después del video existente. SourceEmbed es excelente para anuncios intersticiales, playlists, transmisiones y otros usos en los cuales es posible que desees reproducir varios videos usando un solo elemento de video.

video.webkitSourceAppend(uint8Array);

WebSockets de objetos binarios

También puedes utilizar arreglos escritos con WebSockets para no tener que convertir todos los datos en string. 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 las bibliotecas de terceros para controlar los arrays escritos.

Bibliotecas de terceros

jDataView

jDataView implementa una corrección de compatibilidad de DataView para todos los navegadores. DataView solía ser una función solo de WebKit, pero ahora es compatible con la mayoría de los navegadores. El equipo de desarrolladores de Mozilla también está creando un parche para habilitar DataView en Firefox.

Eric Bidelman, del equipo de Relaciones con Desarrolladores de Chrome, escribió un ejemplo pequeño de lector de etiquetas MP3 ID3 que utiliza jDataView. Aquí hay 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 escritos es un poco difícil en este momento, pero existe la biblioteca de codificación de strings que te puede ayudar. Stringencoding implementa la especificación de codificación de string de array escrito propuesta, por lo que también es una buena manera de hacerse una idea de lo que se avecina.

A continuación, se muestra un ejemplo de uso básico de 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 para arrays escritos llamada BitView.js. Como su nombre lo indica, funciona de manera muy similar a DataView, excepto que funciona con bits. Con BitView, puedes obtener y establecer el valor de un bit con un desplazamiento de bits determinado en un ArrayBuffer. BitView también tiene métodos para almacenar y cargar ints de 6 y 12 bits en desplazamientos de bits arbitrarios.

Los ints de 12 bits son ideales para trabajar con coordenadas de pantalla, ya que las pantallas tienden a tener menos de 4,096 píxeles en la dimensión más larga. Cuando usas ints de 12 bits en lugar de ints 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 mostraría en tamaño de pantalla. El cambio a las coordenadas 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. Puedes ver una demostración aquí.

A continuación, se muestra 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

Uno de los aspectos más interesantes de los arrays escritos es la manera en que facilitan el manejo de los archivos binarios en JavaScript. En lugar de analizar una cadena carácter por carácter y convertir manualmente los caracteres en números binarios, ahora puedes obtener un ArrayBuffer con XMLHttpRequest y procesarlo directamente con DataView. De este modo, es más fácil, por ejemplo, cargar un archivo MP3 y leer las etiquetas de metadatos que se usarán en tu reproductor de audio. O cárgalo en un archivo shapefile y conviértelo en un modelo de WebGL. También puedes leer las etiquetas Exif de un archivo JPEG y mostrarlas en la app de presentación de diapositivas.

El problema con las XHR de ArrayBuffer es que la lectura de datos del tipo struct del búfer es algo difícil. DataView es bueno para leer varios números a la vez de forma segura en endian, las vistas de arrays escritos son útiles para leer arrays de números endian nativos alineados con el tamaño de un elemento. Lo que sentíamos que faltaba es una forma de leer en arrays y structs de datos de manera conveniente y segura para endian. Ingresa DataStream.js.

DataStream.js es una biblioteca de arrays escritos que lee y escribe escalares, strings, arrays y structs de datos de ArrayBuffers de forma similar a un archivo.

Ejemplo de lectura en un arreglo 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 para leer datos más complejos. Supongamos que tienes un método que lee en 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 structs de datos. El método readStruct toma un array de definición de struct que contiene los tipos de sus miembros. Tiene funciones de devolución de llamada para controlar tipos complejos y también maneja arrays de datos y structs anidados:

// 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 struct es un array plano de pares [name, type]. Los structs anidados se realizan 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 de array y el tercer elemento es la longitud del array (ya sea como un número, como 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 está en uso.

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.

Aquí puedes ver un ejemplo en vivo de lectura en metadatos JPEG. En la demostración, se usa DataStream.js para leer la estructura a nivel de etiqueta del archivo JPEG (junto con algunos análisis EXIF) y jpg.js para decodificar y mostrar la imagen JPEG en JavaScript.

Historia de los arrays escritos

Los arrays Typed tuvieron su comienzo en la etapa inicial de implementación de WebGL, cuando descubrimos que pasar los arrays de JavaScript al controlador gráfico causaba problemas de rendimiento. Con los arrays de JavaScript, la vinculación de WebGL tenía que asignar un array nativo y llenarlo caminando sobre el array de JavaScript y transmitir todos los objetos de JavaScript del array al tipo nativo requerido.

Para corregir el cuello de botella en la conversión de datos, Vladimir Vukicevic de Mozilla escribió CanvasFloatArray: un array de número 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 hacer ningún trabajo adicional en la vinculación. En iteraciones adicionales, se cambió el nombre de CanvasFloatArray a WebGLFloatArray, que ahora se cambió a Float32Array y se dividió en un ArrayBuffer de copia de seguridad y la vista Float32Array de tipo para acceder al búfer. También se agregaron tipos para otros tamaños de números enteros y puntos flotantes, y para las variantes con firma y sin firma.

Consideraciones del diseño

Desde el principio, el diseño de los arrays escritos se basó en la necesidad de pasar datos binarios a bibliotecas nativas de manera 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 E/S de archivos y redes, en las que los datos siempre tienen un endianismo especificado y es posible que no estén alineados para obtener el máximo rendimiento.

El diseño dividido entre el ensamblaje de datos en memoria (mediante las vistas de array escrito) y E/S (con DataView) fue consciente. Los motores JavaScript modernos optimizan en gran medida las vistas de array escrito y logran un alto rendimiento en las operaciones numéricas con estas. Los niveles actuales de rendimiento de las vistas de array escrito fueron posibles gracias a esta decisión de diseño.

Referencias