Используем GPU для повышения производительности JavaScript
Как разработчики, мы всегда ищем возможности улучшить произвоительность приложения. Когда дело доходит до web-приложении, мы, в основном, вносим улучшения в коде.
Но задумывались ли вы когда-нибудь скомбинировать мощь графического процессора в своих web-приложениях для повышения производительности?
Эта статья познакомит вас с библиотекой ускорения JavaScript под названием GPU.js и покажет, как улучшить сложные вычисления.
***
Что такое GPU.js и зачем нам его использовать?
Источник: https://gpu.rocks/#/
В кратце, GPU.js – это библиотека ускорения JavaScript, которую можно использовать для вычислений общего назначения на графических процессорах с использованием JavaScript. Он поддерживается в браузерах, Node.js и TypeScript.
Помимо повышения производительности, я рекомендую использовать GPU.js по нескольким причинам:
- GPU.js использует JavaScript в качестве основы, что позволяет использовать синтаксис JavaScript;
- Он берет на себя ответственность за автоматическую трансляцию JavaScript в язык шейдеров и компилирует их;
- Он может вернуться к обычному движку JavaScript, если в устройстве нет графического процессора. Так что, неудобств в использовании GPU.js. не будет;
- GPU.js также можно использовать для параллельных вычислений. Кроме того, вы можете выполнять несколько вычислений асинхронно как в CPU, так и в GPU одновременно.
Учитывая все это, не вижу причин не использовать GPU.js. Так что, давайте посмотрим, как мы можем начать с этим.
***
Как установить GPU.js?
Установка GPU.js аналогична установке любой другой библиотеки JavaScript.
Для Node.js проекта:
npm install gpu.js --save --- или --- yarn add gpu.js
const { GPU } = require('gpu.js')
--- или ---
import { GPU } from 'gpu.js' const gpu = new GPU()
Для браузера:
Скачайте GPU.js локально или используйте CDN.
<script src="dist/gpu-browser.min.js"></script> --- или --- <script src="//unpkg.com/gpu.js@latest/dist/gpu- browser.min.js"></script> <script src="//cdn.jsdelivr.net/npm/gpu.js@latest/dist/gpu-browser.min.js"></script> <script> const gpu = new GPU() ... </script>
Примечание. Если вы используете Linux, вам необходимо убедиться, что у вас установлены правильные файлы, выполнив:
sudo apt install mesa-common-dev libxi-dev
Вот всё что вам нужно знать об установке и импорте GPU.js. Теперь вы можете начать использовать GPU в своем приложении.
Кроме того, я настоятельно рекомендую разобраться в основных функциях и концепциях GPU.js. Итак, давайте начнем с нескольких основ GPU.js.
***
Совет: создавайте и делитесь независимыми компонентами с Bit
Bit – это ультра-расширяемый инструмент, который позволяет создавать по-настоящему модульные приложения с независимо созданными, версионированными и поддерживаемыми компонентами.
Используйте его для создания модульных приложений и дизайн-системах, создания и доставки микро-интерфейсов или просто обмена компонентами между приложениями.
Компоненты Material UI доступны индивидуально на Bit.dev
***
Создание функции
Вы можете определить функции в GPU.js для запуска в GPU, используя общий синтаксис JavaScript.
const exampleKernel = gpu.createKernel(function () { ... }, settings);
В приведенном выше примере кода показана базовая структура функции GPU.js. Я назвал функцию exampleKernel
. Как видно, я использовал метод createKernel
, которая выполняет вычисления с использованием графического процессора.
Также обязательно определите размер вывода. В приведенном выше примере я использовал параметр, называемый settings
, для назначения размера вывода.
const settings = { output: [100] };
Выходные данные функции ядра могут быть одномерные, двухмерные или трёхмерные, что означает, что у нее может быть до 3 потоков. Вы можете получить доступ к этим потокам в ядре с помощью команды this.thread
.
- 1D: [длина] –
value[this.thread.x]
- 2D: [ширина, высота] –
value[this.thread.y] [this.thread.x]
- 3D: [ширина, высота, глубина] –
value[this.thread.z] [this.thread.y] [this.thread.x]
Наконец, созданная функция может быть вызвана, как и любая другая функция JavaScript, с использованием имени функции: exampleKernel()
***
Поддерживаемые переменные в Kernel
Числа
В функции GPU.js можно использовать любое целое число или число с плавающей запятой.const exampleKernel = gpu.createKernel(function () { const number1 = 10; const number2 = 0.10;
return number1 + number2; }, settings);
Булевые
Булевые значения также поддерживаются в GPU.js, как и в JavaScript.const kernel = gpu.createKernel(function () { const bool = true;
if (bool) { return 1; } else { return 0; } },settings);
Массивы
Вы можете определять числовые массивы любого размера в функциях kernel и возвращать их.const exampleKernel = gpu.createKernel(function () { const array1 = [0.01, 1, 0.1, 10]; return array1; }, settings);
Функции
Использование приватных функций внутри функции kernel также возможно в GPU.js.const exampleKernel = gpu.createKernel(function () { function privateFunction() { return [0.01, 1, 0.1, 10]; }
return privateFunction(); }, settings);
***
Поддерживаемые типы входных данных
В дополнение к указанным выше типам переменных, вы можете передавать функциям kernel несколько типов входных данных.
Числа
Подобно объявлению переменной, вы можете передавать целые числа или числа с плавающей запятой в функции kernel, как показано ниже.
const exampleKernel = gpu.createKernel(function (x) { return x; }, settings);
exampleKernel(25);
Одномерные, двухмерные или трёхмерные массивы чисел
Вы можете передавать типы массивов Array, Float32Array, Int16Array, Int8Array, Uint16Array, uInt8Array в kernel GPU.js.
const exampleKernel = gpu.createKernel(function (x) { return x; }, settings); exampleKernel([1, 2, 3]);
Предварительно выровненные двухмерные и трёхмерные массивы также принимаются функциями ядра. Такой подход значительно ускоряет загрузку, и для этого вам нужно использовать параметр ввода из GPU.js.
const { input } = require('gpu.js'); const value = input(flattenedArray, [width, height, depth]);
HTML Изображения
Передача изображений в функции – это новая вещь, которую мы можем увидеть в GPU.js по сравнению с традиционным JavaScript. С помощью GPU.js вы можете передать одно или несколько изображений HTML в виде массива функции kernel.
// Одно изображение const 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); }; // Несколько изображении const 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; .... // Добавить ещё изображения .... const totalImages = 3; let loadedImages = 0; function onload () { loadedImages++; if (loadedImages === totalImages) { kernel([image1, image2, image3]); document.getElementsByTagName('body')[0].appendChild(kernel.canvas); } };
Помимо вышеперечисленных конфигураций, есть много интересных вещей для экспериментов с GPU.js. Вы можете найти их в их документации. Поскольку теперь вы понимаете несколько конфигураций, давайте напишем функцию с помощью GPU.js и сравним ее производительность.
***
Первая функция с использованием GPU.js
Объединив все, что мы обсуждали ранее, я написал небольшое Angular приложение для сравнения производительности вычислений GPU и CPU путем умножения двух массивов на 1000 элементов.
Шаг 1 - Функция для создания числовых массивов из 1000 элементов
Я сгенерирую 2D-массив с 1000 числами для каждого элемента и использую их для вычислений на следующих шагах.
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) } } }
Шаг 2 – функция ядра
Это самая важная функция в этом приложении, поскольку все вычисления графического процессора происходят внутри него. Здесь функция multiplyMatrix
получит в качестве входных данных два числовых массива и размер матрицы. Затем он умножит два массива и вернет общую сумму измеряя время с помощью Performance API.
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[][]; }
Шаг 3 – функция умножения ЦП.
Это традиционная функция TypeScript, используемая для измерения времени вычислений для одних и тех же массивов.
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; }
Вы можете найти полный демо-проект на моём GitHub аккаунте.
CPU vs GPU – Сравнение производительности
А теперь пора посмотреть, верна ли вся эта шумиха вокруг GPU.js и вычислений на GPU. Поскольку в предыдущем разделе я создал приложение Angular, я использовал его для измерения производительности.
Как вы можете увидеть, программа на GPU потребовало всего 799 мс для вычислений, в то время как на CPU заняло 7511 мс, что почти в 10 раз больше.
Не останавливаясь на этом, я провел те же тесты в течение пары циклов, изменяя размер массива.
Во-первых, я попробовал использовать массивы меньшего размера и заметил, что CPU занимает меньше времени, чем графический процессор. Например, когда я уменьшил размер массива до 10 элементов, CPU потребовалось всего 0,14 мс, а GPU - 108 мс.
Но по мере увеличения размера массива наблюдался явный разрыв между временем, затрачиваемым на GPU и CPU. Как вы можете видеть на графике выше, GPU оказался победителем.
***
Вывод
Основываясь на моем эксперименте с использованием GPU.js, он может повысить производительность приложений JavaScript.
Но мы должны помнить об использовании GPU только для сложных задач. В противном случае мы будем тратить ресурсы впустую и, в конечном итоге, снизим производительность приложения, как показано на приведенном выше графике.
Однако, если вы еще не пробовали GPU.js, я приглашаю вас всех попробовать его и поделиться своим опытом.
Спасибо, что прочитали!!!
***
От переводчика
Статься переведена с английского на Medium: Оригинальная статья
Это мой первый пост на этой площадке, так что буду рад вашим советам!
Комментарии 1
Авторизуйтесь чтобы оставить комментарий
Laura Meir · Июль 29, 2024 16:43
Сложно))