Objetos transferíveis: ultrarrápida

O Chrome 13 introduziu o envio de ArrayBuffers de/para um Web worker usando um algoritmo chamado clonagem estruturada. Isso permitiu que a API postMessage() aceitasse mensagens que não eram apenas strings, mas tipos complexos como File, Blob, ArrayBuffer e objetos JSON. A clonagem estruturada também é compatível em versões mais recentes do Firefox.

Quanto mais rápido, melhor

A clonagem estruturada é ótima, mas ainda é uma operação de cópia. A sobrecarga da transmissão de um ArrayBuffer de 32 MB para um worker pode ser de centenas de milissegundos. As novas versões dos navegadores apresentam uma grande melhoria de desempenho na transmissão de mensagens, chamada de Objetos transferíveis.

Com objetos transferíveis, os dados são transferidos de um contexto para outro. É zero cópia, o que melhora muito o desempenho do envio de dados para um worker. Pense nisso como uma passagem de referência se você estiver no mundo C/C++. No entanto, ao contrário da passagem por referência, a "versão" do contexto de chamada não fica mais disponível após a transferência para o novo contexto. Por exemplo, ao transferir um ArrayBuffer do app principal para o worker, o ArrayBuffer original é apagado e não pode mais ser usado. O conteúdo é transferido (literalmente) para o contexto do worker.

Para jogar com transferíveis, há uma nova versão do postMessage() que oferece suporte a objetos transferíveis:

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

No caso do worker, o primeiro argumento é a mensagem ArrayBuffer. O segundo argumento é uma lista de itens que precisam ser transferidos. Neste exemplo, você especificaria arrayBuffer na lista transferível.

Demonstração do comparativo de mercado

Para conferir os ganhos de desempenho dos itens transferíveis, criei uma demonstração (em inglês).

A demonstração envia um ArrayBuffer de 32 MB para um worker e retorna usando postMessage(). Se o navegador não for compatível com transferência, o exemplo recorrerá à clonagem estruturada. Em média, cinco são executados em navegadores diferentes. Isto é o que eu consegui:

Tabela de comparação entre clonagem estruturada x objetos transferíveis

Em um MacBook Pro/10.6.8/2,53 GHz/Intel Core 2 Duo, o FF foi o mais rápido usando a clonagem estruturada. Em média, levava 302 ms para enviar o ArrayBuffer de 32 MB a um worker e postá-lo de volta na linha de execução principal (RRT, na sigla em inglês). Comparando com os transferíveis, o mesmo teste levou 6,6 ms. Isso é um grande aumento no desempenho!

Com esses tipos de velocidade, é possível transmitir texturas/malhas enormes de WebGL entre um worker e o app principal.

Detecção de recursos

A detecção de atributos é um pouco complicada. Minha recomendação é enviar um pequeno ArrayBuffer para o worker. Se o buffer for transferido e não copiado, o .byteLength dele vai para 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.
}

Suporte: no momento, Google Chrome 17 ou superior, Firefox, Opera, Safari e IE10 ou superior

Atualizado em 13/12/2011: o snippet de código para mostrar a assinatura webkitPostMessage() é diferente para janela e worker. Atualizado em 03/11/2016: prefixos de fornecedores e snippets de código atualizados