Usando GPU para melhorar o desempenho do JavaScript
Usando GPU para melhorar o desempenho do JavaScript
Torne suas aplicações 10 vezes mais rápidas com GPU.js
Como desenvolvedores, sempre buscamos oportunidades para melhorar o desempenho da aplicação. Quando se trata de aplicações web, fazemos principalmente essas melhorias no código.
Mas você já pensou em combinar o poder da GPU em seus aplicativos web para aumentar o desempenho?
Este artigo irá apresentá-lo a uma biblioteca de aceleração JavaScript chamada GPU.js e mostrar-lhe como melhorar computações complexas.
O que é GPU.js e por que devemos usá-la?
Em suma, GPU.js é uma biblioteca de aceleração JavaScript que pode ser usada para cálculos de uso geral em GPUs usando JavaScript. Ele suporta navegadores, Node.js e TypeScript.
Além do aumento de desempenho, existem várias razões pelas quais recomendo o uso de GPU.js:
- GPU.js usa JavaScript como base, permitindo que você use sintaxe JavaScript.
- Ele assume a responsabilidade de transpilar automaticamente JavaScript em linguagem shader e compilá-los.
- Ele pode cair de volta no motor JavaScript normal se não houver GPU no dispositivo. Então, não haverá nenhuma desvantagem em usar GPU.js.
- GPU.js também pode ser usada para cálculos paralelos. Além disso, você pode realizar vários cálculos assincronicamente em CPU e GPU ao mesmo tempo.
Com todas essas coisas juntas, não vejo razão para não usar GPU.js. Então vamos ver como podemos começar com isso.
Como configurar GPU.js?
Instalar GPU.js para seus projetos é semelhante a qualquer outra biblioteca JavaScript.
Para projetos de Nó
npm install gpu.js --save
or
yarn add gpu.jsimport { GPU } from ('gpu.js')
--- or ---
const { GPU } = require('gpu.js')
--- or ---
import { GPU } from 'gpu.js'; // Use this for TypeScriptconst gpu = new GPU();
Para Bowsers
Baixar GPU.js localmente ou use seu CDN.
<script src="dist/gpu-browser.min.js"></script>--- or ---<script
src="https://unpkg.com/gpu.js@latest/dist/gpu- browser.min.js">
</script>
<script
rc="https://cdn.jsdelivr.net/npm/gpu.js@latest/dist/gpu-browser.min.js">
</script><script>
const gpu = new GPU();
...
</script>
Nota: Se você estiver usando o Linux, você precisa garantir que você tenha arquivos corretos instalados executando:
sudo apt install mesa-common-dev libxi-dev
Isso é o que você precisa saber sobre a instalação e importação de GPU.js. Agora, você pode começar a usar a programação de GPU em sua aplicação.
Além disso, recomendo muito entender as funções e conceitos essenciais da GPU.js. Então, vamos começar com poucos fundamentos de GPU.js.
Dica: Construir e compartilhar componentes independentes com bit
Bit é uma ferramenta ultra-extensível que permite criar aplicativos verdadeiramente modulares com componentes independentes de autoria, versão e manutenção.
Use-o para criar aplicativos modulares e sistemas de design, autor e fornecer micro frontends, ou simplesmente compartilhar componentes entre aplicativos.
Criando funções
Você pode definir funções em GPU.js para ser executado em GPU, usando sintaxe JavaScript geral.
const exampleKernel = gpu.createKernel(function() {
...
}, settings);
A amostra de código acima mostra a estrutura básica de uma função gpu.js. Eu nomeei a função como você pode ver, lá eu usei a função que faz cálculos aproveitando a GPU.exampleKernel.
createKernel
Além disso, é obrigatório definir o tamanho da saída. No exemplo acima, usei um parâmetro chamado configurações para atribuir tamanho de saída.
const settings = {
output: [100]
};
A saída de uma função de kernel pode ser 1D, 2D ou 3D, o que significa que pode ter até 3 threads. Você pode acessar esses segmentos dentro do kernel usando o comando.this.thread
- 1D : [comprimento] —
value[this.thread.x]
- 2D : [largura, altura] —
value[this.thread.y][this.thread.x]
- 3D: [largura, altura, profundidade] —
value[this.thread.z][this.thread.y][this.thread.x]
Finalmente, a função criada pode ser invocada como qualquer outra função JavaScript usando o nome da função: exampleKernel()
Variáveis suportadas para kernels
número
Você pode usar qualquer inteiro ou flutuar dentro de uma função gpu.js.
const exampleKernel = gpu.createKernel(function() {
const number1 = 10;
const number2 = 0.10;
return number1 + number2;
}, settings);
booleano
Os valores booleanos também são suportados em GPU.js semelhante ao JavaScript.
const kernel = gpu.createKernel(function() {
const bool = true;
if (bool) {
return 1;
}else{
return 0;
}
},settings);
Matrizes
Você pode definir matrizes numé séries de qualquer tamanho dentro das funções do kernel e devolvê-las.
const exampleKernel = gpu.createKernel(function() {
const array1 = [0.01, 1, 0.1, 10];
return array1;
}, settings);
Funções
O uso de funções privadas dentro da função do kernel também é permitido em GPU.js
const exampleKernel = gpu.createKernel(function() {
function privateFunction() {
return [0.01, 1, 0.1, 10];
}
return privateFunction();
}, settings);
Tipos de entrada suportados
Além dos tipos de variável acima, você pode passar vários tipos de entrada para funções de kernel.
Números
Semelhante à declaração variável, você pode passar inteiros ou números flutuantes para funções de kernel como abaixo.
const exampleKernel = gpu.createKernel(function(x) {
return x;
}, settings);exampleKernel(25);
Matriz 1D,2D ou 3D de Números
Você pode passar tipos de matrizes de , , , ,.js ,Array
Float32Array
Int16Array
Int8Array
Uint16Array
uInt8Array
const exampleKernel = gpu.createKernel(function(x) {
return x;
}, settings);exampleKernel([1, 2, 3]);
As matrizes 2D e 3D pré-achatadas também são aceitas por funções de kernel. Esta abordagem torna os uploads muito mais rápidos e você tem que usar a opção de GPU.js para isso.input
const { input } = require('gpu.js');
const value = input(flattenedArray, [width, height, depth]);
Imagens HTML
Passar imagens para funções é uma novidade que podemos ver em GPU.js em comparação com o JavaScript tradicional. Com GPU.js, você pode passar uma ou muitas imagens HTML como uma matriz para a função do kernel.
//Single Imageconst kernel = gpu.createKernel(function(image) {
...
})
.setGraphical(true)
.setOutput([100, 100]);
const image = document.createElement('img');
image.src = 'image1.png';
image.onload = () => {
kernel(image);
document.getElementsByTagName('body')[0].appendChild(kernel.canvas);
};//Multiple Imagesconst kernel = gpu.createKernel(function(image) {
const pixel = image[this.thread.z][this.thread.y][this.thread.x];
this.color(pixel[0], pixel[1], pixel[2], pixel[3]);
})
.setGraphical(true)
.setOutput([100, 100]);
const image1 = document.createElement('img');
image1.src = 'image1.png';
image1.onload = onload;
....
//add another 2 images
....
const totalImages = 3;
let loadedImages = 0;
function onload() {
loadedImages++;
if (loadedImages === totalImages) {
kernel([image1, image2, image3]);
document.getElementsByTagName('body')[0].appendChild(kernel.canvas);
}
};
Além das configurações acima, há muitas coisas interessantes para experimentar com GPU.js. Você pode encontrá-los em sua documentação. Como agora você entende várias configurações, vamos escrever uma função com GPU.js e comparar seu desempenho.
Primeira função usando GPU.js
Combinando todas as coisas que discutimos anteriormente, escrevi um pequeno aplicativo angular para comparar o desempenho das computação de GPU e CPU multiplicando duas matrizes com 1000 elementos.
Passo 1 — Função para gerar matrizes numésias com 1000 elementos
Vou gerar uma matriz 2D com 1000 números para cada elemento e usá-los para cálculos nos próximos passos.
generateMatrices() {
this.matrices = [[], []];
for (let y = 0; y < this.matrixSize; y++) {
this.matrices[0].push([])
this.matrices[1].push([])
for (let x = 0; x < this.matrixSize; x++) {
const value1 = parseInt((Math.random() * 10).toString())
const value2 = parseInt((Math.random() * 10).toString())
this.matrices[0][y].push(value1)
this.matrices[1][y].push(value2)
}
}
}
Passo 2 — Função do kernel
Esta é a função mais crucial nesta aplicação, já que todos os cálculos de GPU acontecem dentro disso. Aqui a função receberá dois conjuntos numédes e o tamanho da matriz como entrada. Em seguida, ele multiplicará duas matrizes e devolverá a soma total enquanto mede o tempo usando API de desempenho.multiplyMatrix
gpuMultiplyMatrix() {
const gpu = new GPU();
const multiplyMatrix = gpu.createKernel(function (a: number[][], b: number[][], matrixSize: number) {
let sum = 0;
for (let i = 0; i < matrixSize; i++) {
sum += a[this.thread.y][i] * b[i][this.thread.x];
}
return sum;
}).setOutput([this.matrixSize, this.matrixSize]) const startTime = performance.now();
const resultMatrix = multiplyMatrix(this.matrices[0], this.matrices[1], this.matrixSize);
const endTime = performance.now();
this.gpuTime = (endTime - startTime) + " ms";
console.log("GPU TIME : "+ this.gpuTime);
this.gpuProduct = resultMatrix as number[][];
}
Passo 3: função de multiplicação da CPU.
Esta é uma função TypeScript tradicional usada para medir o tempo computacional para as mesmas matrizes.
cpuMutiplyMatrix() {
const startTime = performance.now();
const a = this.matrices[0];
const b = this.matrices[1]; let productRow = Array.apply(null, new Array(this.matrixSize)).map(Number.prototype.valueOf, 0);
let product = new Array(this.matrixSize);
for (let p = 0; p < this.matrixSize; p++) {
product[p] = productRow.slice();
}
for (let i = 0; i < this.matrixSize; i++) {
for (let j = 0; j < this.matrixSize; j++) {
for (let k = 0; k < this.matrixSize; k++) {
product[i][j] += a[i][k] * b[k][j];
}
}
} const endTime = performance.now();
this.cpuTime = (endTime — startTime) + “ ms”;
console.log(“CPU TIME : “+ this.cpuTime);
this.cpuProduct = product;
}
Você pode encontrar o projeto de demonstração completo na minha conta do GitHub.
CPU vs GPU — Comparação de Desempenho
Agora é hora de ver se todo esse zumbido em torno da GPU.js e da computação de GPU é verdade ou não. Desde que criei um aplicativo Angular na seção anterior, usei-o para medir o desempenho.
Como você pode ver claramente, a programação de GPU levou apenas 799ms para os cálculos, enquanto a CPU levou 7511ms, o que é quase 10 vezes maior.
Sem parar de lá, fiz os mesmos testes por alguns ciclos mudando o tamanho da matriz.
Primeiro, tentei com tamanhos menores de matriz, e notei que a CPU tinha levado menos tempo que a GPU. Por exemplo, quando reduzi o tamanho do array para 10 elementos, a CPU só levou 0,14ms enquanto a GPU levou 108 ms.
Mas à medida que o tamanho da matriz aumenta, havia uma clara diferença entre o tempo gasto pela GPU e cpu. Como você pode ver no gráfico acima, GPU foi o vencedor.
conclusão
Com base no meu experimento usando GPU.js, ele pode aumentar o desempenho dos aplicativos JavaScript.
Mas, devemos estar atentos ao uso de GPU apenas para tarefas complexas. Caso contrário, estaremos desperdiçando recursos e, em última análise, reduzirá o desempenho da aplicação, como mostrado no gráfico acima.
No entanto, se você ainda não experimentou GPU.js, convido todos vocês a usá-la e compartilhar sua experiência na seção de comentários.
Obrigado por ler !!!.
Comentários
Postar um comentário