Explorando Multithreads no JavaScript com web workers e GPU.js

Javascript

Por muito tempo o javascript foi visto como um complemento para a programação do lado cliente, junto com seus companheiros HTML e CSS. Mas o nosso amigo Javascript era um moleque travesso e resolveu se aventurar um pouco mais. Com o passar do tempo o javascript deixou de ser apenas aquele que dá “vida” aos elementos visuais e passou a ganhar novos recursos como Ajax, XMLHttpRequest, etc. Do lado das bibliotecas a grande virada de chave foi com a chegada do nodejs, assim o menino travesso começou a ser usado do lado do servidor. 

O javascript por si só é uma linguagem de sequência única, uma única thread executada do início ao fim, ou seja, não poderia ser executado vários trechos de scripts ao mesmo tempo. Então a grande dúvida é: Como executar threads simultâneas e acabar com as janelas travadas esperando resposta do servidor ? 

WEB WORKER

O HTML5 trouxe muitos recursos, entre eles o conhecido web workers. Resumidamente, o principal objetivo dessa API é executar em uma thread separada, scripts com tarefas que serão custosas e demoradas, como por exemplo, cálculos complicados e pesados.

Os workers trabalham com trocas de mensagens para terem paralelismo, permitindo que a aplicação continue rodando na thread principal de forma fluída sem travar ou quebrar a experiência do usuários.

Exemplo 1 sem web worker

Sem a utilização dos workers

Chamando essa função apenas chamando no head do arquivo.html vai exceder a pilha de chamadas permitida pelo browser

Exemplo 1 com web worker

Com a utilização dos workers

Com a utilização do worker uma novo método é feito no próprio arquivo.html que faz a chamada do worker.js e a cada nova chamada do mesmo método uma nova thread é gerada. 

Resposta da chamada, em algum momento o browser vai encerrar a criação de threads, no exemplo acima o chrome permitiu a criação de 6345 threads e o firefox permitiu a criação de 28038 threads

Exemplo 2 com troca de mensagens entre o main e worker. 

Observação: Os dados enviados entre a thread principal e os trabalhadores são copiados em vez de compartilhados. Essa questão é importante devido a alocação de memória e na velocidade de transferência de dados.

main.js

worker.js

  • O aplicativo criou um worker no main.js, que executa o código de worker.js
  • Envia ao trabalhador uma mensagem com a string ‘Happy Birthday’
  • O trabalhador, que tinha um ouvinte de evento para ‘mensage’, recebeu a mensagem e executou o código.
  • O trabalhador acrescentou “a mim mesmo!” para os dados da mensagem criando “Feliz Aniversário para mim mesmo!” e envia isso como dados dentro de uma mensagem de volta para main.js
  • Main.js, que também tinha um ouvinte de evento para a mensagem, exibe com console.log ‘Happy Birthday to myself!’.

GPU.JS

O gpu.js é uma biblioteca GPGPU (Programação para fins gerais em unidades de  processamento gráfico) que permite entregar cálculos pesados à GPU para operação e saídas super rápidas.

Como um primeiro exemplo simples e de execução no próprio browser, podemos fazer uma simples multiplicação de matrizes de tamanho 512 x 512. 

Os arquivos gpu.min.js e gpu-core.min.js que estão disponíveis no repositório, devem ser importados no arquivos index.html que é interpretado pelos browsers, nesse exemplo serão utilizados, google chrome e firefox. 

A próxima etapa é configurar a GPU para executar os cálculos. Os arquivos da lib exportam uma função global denominada GPU que será usada para criar uma nova instância gpu. Alguns parâmetros podem ser usados, nesse caso o mode será utilizado para especificar onde será executada a função, GPU ou CPU. 

O método createKernel, cria um “Kernel” (um termo abstrato para o que poderia ser, na verdade function) que pode chamar de JS. “Por trás dos panos”, o código é compilado para os shaders GLSL usando um analisador AST e baseado em jison. Isso garante que o código escrito dentro do kernel será executado em uma GPU.

shaders é um conjunto de instruções que definem o comportamento da superfície dos objetos. Basicamente são utilizados para aplicar efeitos como reflexos do ambiente na lataria de um carro em um game ou a movimentação da água enquanto um personagem está nadando.

GLSL é uma linguagem de shading shaders de alto nível baseada na linguagem C. Foi criada com o objetivo de dar mais controle do pipeline de gráficos sem precisar usar assembly ou outras linguagens específicas de hardware (baixo nível).

AST(Abstract Syntax Tree). Podemos pensar no AST como um mapa  para o código — é uma forma de entender como um pedaço de código é estruturado.

jison Uma API parser para criar analisadores em JavaScript. O Script gerado é usada para analisar entradas, aceitar, rejeitar ou executar ações com base na entrada. 

Usando o método .setDimensions é possível ter acesso as dimensões da thread, podemos imaginar como comprimentos de um for quando usado na CPU por exemplo. 

Um problema inerente é a questão da transferência dos comandos enviados da CPU para a caixa preta GPU, essa questão acaba se tornando um grande gargalo, principalmente quando envolve a realização de diversas operações matemáticas na GPU.  No entanto no GPU.js podemos reverter esse problema deixando valores na GPU. Ao definir o outputToTexture como True, podemos garantir que não teremos penalidades de transferência.

O código da demonstração basicamente adiciona 512*512 elementos em uma matriz JavaScript. (1D)  e, em seguida, divide-os em 512 partes, o que significa que, no final , temos um array 2D de tamanho 512*512. (Cada elemento da matriz tem elementos filhos.)

Resultados do teste gpu.js browsers chrome e firefox USANDO .outputToTexture(true)

Figura 1: Firefox Figura 2: Chrome

Resultados do teste gpu.js browsers chrome e firefox SEM USAR .outputToTexture(true)

Figura 3: Firefox     Figura 4: Chrome

Código que realiza o cálculo entre as duas matrizes, m e n usando a cpu.

Código que realiza cálculo entre as duas matrizes, a e b usando gpu.

Web Worker: O jeito JS de fazer multithread. https://bit.ly/2JwUvp9

Shaders: O que são e para que servem? Felipe Demartini https://bit.ly/2JaAXIv

GLSL Language Specification, Version 4.10.6. Khronos Group. Acessado em 3 de julho de 2019. https://bit.ly/2XkfLnf

An API for creating parsers in JavaScript https://zaa.ch/jison/

Introducing gpu.js: GPU Accelerated JavaScript. Acessado em 3 de Julho de 2019. https://bit.ly/2MAkGeO