Oggetti trasferibili - Velocissime

Chrome 13 ha introdotto l'invio di ArrayBuffer da/verso un web worker utilizzando un algoritmo chiamato clonazione strutturata. In questo modo l'API postMessage() ha potuto accettare messaggi che non erano solo stringhe, ma di tipi complessi come File, Blob, ArrayBuffer e oggetti JSON. La clonazione strutturata è supportata anche nelle versioni successive di Firefox.

Più veloce è meglio

La clonazione strutturata è ottima, ma è comunque un'operazione di copia. L'overhead del passaggio di un ArrayBuffer da 32 MB a un worker può essere di centinaia di millisecondi. Le nuove versioni dei browser presentano un enorme miglioramento delle prestazioni per la trasmissione dei messaggi, denominate oggetti trasferibili.

Nel caso di oggetti trasferibili, i dati vengono trasferiti da un contesto all'altro. Si tratta di zero-copy, che migliora notevolmente le prestazioni di invio dei dati a un worker. È una sorta di "pass-by-reference" se appartieni al mondo C/C++. Tuttavia, a differenza del passaggio per riferimento, la "versione" del contesto di chiamata non è più disponibile una volta trasferita nel nuovo contesto. Ad esempio, quando trasferisci un ArrayBuffer dalla tua app principale a Worker, l'ArrayBuffer originale viene cancellato e non può più essere utilizzato. I suoi contenuti vengono (letteralmente) trasferiti al contesto del worker.

Per poter usare i contenuti trasferibili, esiste una nuova versione di postMessage() che supporta gli oggetti trasferibili:

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

Per il caso dei worker, il primo argomento è il messaggio ArrayBuffer. Il secondo argomento è un elenco di elementi che devono essere trasferiti. In questo esempio, devi specificare arrayBuffer nell'elenco dei contenuti trasferibili.

Demo benchmark

Per verificare l'incremento del rendimento dei prodotti trasferibili, ho preparato una demo.

La demo invia un file ArrayBuffer di 32 MB a un worker e torna indietro utilizzando postMessage(). Se il tuo browser non supporta i contenuti trasferibili, l'esempio esegue un ricorso alla clonazione strutturata. In media 5 viene eseguito in browser diversi, ecco cosa ho ottenuto:

Grafico di confronto tra clonazione strutturata e oggetti trasferibili

Su un MacBook Pro/10.6.8/2.53 GHz/Intel Core 2 Duo, FF è stato il più veloce nell'utilizzo della clonazione strutturata. In media, sono stati necessari 302 ms per inviare il ArrayBuffer di 32 MB a un worker e pubblicarlo di nuovo nel thread principale (RRT - Round Trip Time). Confrontando i dati trasferibili, lo stesso test ha richiesto 6,6 ms. Una grande spinta in termini di prestazioni!

La presenza di questo tipo di velocità consente di passare senza soluzione di continuità grandi texture/macchine WebGL tra un'app worker e l'app principale.

Rilevamento delle caratteristiche

Il rilevamento delle caratteristiche è un po' complicato in questa. Ti consiglio di inviare un ArrayBuffer di piccole dimensioni al tuo lavoratore. Se il buffer viene trasferito e non copiato, il valore di .byteLength passerà 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.
}

Supporto: attualmente Chrome 17 o versioni successive, Firefox, Opera, Safari e IE10 o versioni successive

Aggiornato (13/12/2011): lo snippet di codice per mostrare la firma webkitPostMessage() è diverso per finestra e worker. Aggiornamento (3/11/2016): rimozione dei prefissi dei fornitori e degli snippet di codice aggiornati