Objetos transferibles: muy rápido

Chrome 13 introdujo el envío de ArrayBuffer desde o hacia un trabajador web con un algoritmo llamado clonación estructurada. Esto permitió que la API de postMessage() aceptara mensajes que no solo eran cadenas, sino tipos complejos como File, Blob, ArrayBuffer y objetos JSON. La clonación estructurada también es compatible con versiones posteriores de Firefox.

Cuanto más rápido, mejor

La clonación estructurada es excelente, pero sigue siendo una operación de copia. La sobrecarga de pasar un ArrayBuffer de 32 MB a un trabajador puede ser de cientos de milisegundos. Las versiones nuevas de los navegadores contienen una gran mejora de rendimiento para el envío de mensajes, llamada Objetos transferibles.

Con los objetos transferibles, los datos se transfieren de un contexto a otro. No requiere copia, lo que mejora en gran medida el rendimiento de enviar datos a un trabajador. Piensa en ello como paso por referencia si vienes del mundo de C/C++. Sin embargo, a diferencia del paso por referencia, la "versión" del contexto de llamada ya no está disponible una vez que se transfiere al contexto nuevo. Por ejemplo, cuando transfieres un ArrayBuffer de tu app principal al trabajador, el ArrayBuffer original se borra y ya no se puede usar. Su contenido se transfiere (literalmente) al contexto de trabajador.

Para jugar con elementos transferibles, hay una nueva versión de postMessage() que admite objetos transferibles:

worker.postMessage(arrayBuffer, [transferableList]);
window.postMessage(arrayBuffer, targetOrigin, [transferableList]);

En el caso del trabajador, el primer argumento es el mensaje ArrayBuffer. El segundo argumento es una lista de elementos que se deben transferir. En este ejemplo, especificarías el arrayBuffer en la lista transferible.

Demostración de comparativas

Para ver los aumentos de rendimiento de los elementos transferibles, creé una demostración.

La demostración envía un ArrayBuffer de 32 MB a un trabajador y viceversa con postMessage(). Si tu navegador no admite elementos transferibles, el feed de ejemplo recurrirá a la clonación estructurada. Con un promedio de 5 ejecuciones en diferentes navegadores, obtuve lo siguiente:

Gráfico de comparación entre la clonación estructurada y los objetos transferibles

En una MacBook Pro/10.6.8/2.53 GHz/Intel Core 2 Duo, FF fue la más rápida con la clonación estructurada. En promedio, se tardaron 302 ms en enviar el ArrayBuffer de 32 MB a un trabajador y volver a publicarlo en el subproceso principal (RRT: tiempo de ida y vuelta). En comparación con los elementos transferibles, la misma prueba tardó 6.6 ms. Eso es un gran aumento de rendimiento.

Tener este tipo de velocidades permite que las texturas o mallas masivas de WebGL se pasen sin problemas entre un trabajador y la app principal.

Detección de componentes

La detección de características es un poco complicada con esta. Mi recomendación es que envíes un ArrayBuffer pequeño a tu trabajador. Si el búfer se transfiere y no se copia, su .byteLength irá a 0:

var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
    alert('Transferables are not supported in your browser!');
} else {
    // Transferables are supported.
}

Compatibilidad: Actualmente, Chrome 17 y versiones posteriores, Firefox, Opera, Safari y IE10 y versiones posteriores

Actualización (13/12/2011): El fragmento de código para mostrar la firma de webkitPostMessage() es diferente para el subproceso y el trabajador. Actualización (3/11/2016): Se quitaron los prefijos de los proveedores y se actualizaron los fragmentos de código.