Найти тему

Node.JS приложения с почти нативной производительность — нативные модули

Оглавление

Вступление

Думаю, что многие сталкивались с проблемой скорости работы приложений, написанных на Node.JS, ведь JavaScript не отличается высокой скоростью при сравнении с тем же C++. Но мало кто знает, что от этой проблемы можно избавиться при помощи написания нативных V8 модулей. Давайте разберёмся, что же это такое.

Что же такое V8?

V8 - последняя версия движка JavaScript, который распространяется с открытым исходным кодом и разрабатывается компанией Google. Помимо этого движка существуют и другие, но производительность V8 в разы выше них. Большая часть браузеров в данный момент используют движок Chromium, который, в свою очередь, написан на движке V8.

Что такое нативный модуль в Node.JS?

Нативный модуль представляет из себя скомпилированный C++ код на движке V8 (Node.JS тоже написан на нём), который работает с примерно такой же скоростью, что и код на чистом C++. Далее в статье мы сравним скорость программы, которая написана на чистом JS с нативным модулем V8.

Помимо повышения производительности мы можем использовать V8 модули для взаимодействия с внешним или внутренним API, то есть появляется возможность использования win32 API, DLL библиотек (в чистом Node.JS их использовать нельзя), а также СОВЕРШЕННО всё, что можно сделать на C++.

Почему бы не использовать обычный C++?

Конечно, многие могут сказать, что это бессмысленно, ведь можно просто писать на C++ без лишней мороки, но не торопитесь с выводами. Тот, кто хотя бы раз углублялся в тот же win32 API, понимает, что создание интерфейсов без использования сторонних библиотек, например, QT, - занятие адской сложности. Что же мы можем получить в связке с Node.JS? В Node.JS существуют такие фреймворки, как Electron и NW.JS, которые упрощают создание пользовательских интерфейсов во много раз, т.к. их создание на стеке HTML + CSS + React/Vue/Angular - задача легче лёгкого, а уже это позволяет увеличить скорость разработки, облегчить дальнейшее обновление интерфейса, упростить в сотни раз процесс создания анимации элементов.

Фреймворк NW.JS
Фреймворк NW.JS

Фреймворк Electron
Фреймворк Electron

Подготовка к работе

Для компиляции модулей нам потребуется NPM пакет node-gyp.

Все инструкции по его установке в доступном виде могут быть найдены на странице GitHub репозитория, поэтому не вижу смысла расписывать его более подробно.

Начало работы

1. Первым делом напишем простой скрипт на JS и засечём время его исполнения...

Код index.js
Код index.js
Результат выполнения скрипта
Результат выполнения скрипта

Как вы можете заметить: для выполнения такой, казалось бы, простой задачи JS требуется 2 миллисекунды.

2. Теперь создаём файл module.cc со следующим исходным кодом:

Код нативного модуля
Код нативного модуля

Давайте разберёмся, что к чему...

Первым делом мы подключаем заголовочный файл node.h, который автоматически добавит к проекту пакет node-gyp.

Аргументом функции является аргумент, в который Node.JS передаёт данные о текущем состоянии скрипта во время вызова функции.

Функция GetIsolate возвращает текущий JS контекст, с помощью которого мы можем выполнять операции по типу перевода значений int, bool, std::string и т.д. в понятный для JS вид, что и делается на 16-ой строке кода.

Функция GetReturnValue получает объект, в котором хранится значение, которое будет возвращено в JS.

Функция Initialize содержит в себе код инициализации модуля, в котором присваиваются все созданные функции через функцию NODE_SET_METHOD, первым аргументом которого является exports, вторым - название переменной после экспорта в JS, третьим же - сама функция.

3. Следующим шагом нам нужно создать файл binding.gyp, в котором будет находиться вся информации о компиляции нашего модуля.

Более подробно об этом файле и его содержимом вы можете прочитать в официальной/неофициальной документации, но в данном случае нам нужно только это:

Содержимое binding.gyp
Содержимое binding.gyp

4. Открываем CMD/PowerShell в директории с binding.gyp и module.cc, пишем команды:

$ node-gyp configure

$ node-gyp build

Если при выполнении одной из этих команд у вас возникли ошибки, то убедитесь, что вы корректно выполнили установку/настройку пакета node-gyp (инструкция настройки для разных ОС находится на странице официального репозитория).

Если же компиляция прошла успешно, то по пути build/Release у вас должен лежать файл с расширение node (в нашем случае - test-native-module.node).

5. Подключим наш модуль в index.js файл и выполним функцию с подсчётом времени выполнения.

Обновлённый код index.js
Обновлённый код index.js

6. Выполняем index.js для того, чтобы, наконец, узнать результат.

Результат выполнения скрипта
Результат выполнения скрипта

Думаю, разница видна невооружённым глазом. Несмотря на разную скорость выполнения функций (время отображено в МИКРОСЕКУНДАХ, а НЕ в миллисекундах, как это было во время прошлого измерения), мы можем заметить, что обе функции возвращают одно и то же значение.

Итог

Что же в итоге? Мы можем понять, что скорость выполнения нативной функции выше обычной аж в 30 раз, что даёт огромный прирост производительности, особенно если задача будет сложнее.

В следующих статьях мы рассмотрим использование win32 API, примеры с выполнением реальных задач при помощи нативных модулей (например, интеграция со SteamWorks API в Node.JS).

Ссылки

Репозиторий node-gyp: https://github.com/nodejs/node-gyp

Документация по модулям: https://nodejs.org/api/addons.html

V8 Engine API: https://v8docs.nodesource.com/node-4.8/