- PVSM.RU - https://www.pvsm.ru -
Предлагаю вашему вниманию перевод статьи "Neon: Node + Rust [1]".
Javascript программистам, которых заинтриговала rust-овская тема бесстрашного программирования [2] (сделать системное [низкоуровневое] программирование безопасным и прикольным), но при этом ждущих вдохновений или волшебных пендалей — их есть у меня! Я тут поработал немного над Neon [3] — набором API и тулзов, которые делают реально легким процесс написания нативных расширений под Node на Rust.
Я хотел сделать процесс настолько легким, насколько возможно (Ларри Уолл тоже с этого начинал, прим. переводчика) и написал для этого Neon-cli [4], консольную утилиту, которая в одну команду генерит шаблон Neon проекта, который собирается ничем иным как привычным npm install
Тут все очень просто. Для того что бы собрать наш первый модуль с Neon, ставим Neon-cli: npm install -g neon-cli
, затем создаем, собираем и запускаем:
% neon new hello
...follow prompts...
% cd hello
% npm install
% node -e 'require("./")'
Для особо неверующих я тут выложил скринкаст [5], так что можете сходить и убедиться.
Чтобы продемонстрировать возможности Neon-а, я создал небольшое демо [6] (считает количество слов). Демка простая — читаем полное собрание пьес Шекспира и считаем число вхождений слова «тебя» (I Take Thee at thy Word — цитата из Ромео и Джульетты) Сначала я попытался сделать это на ванильном javascript. Для начала мой код разбивает текст на строки и считает количество найденных вхождений для каждой строчки:
function search(corpus, search) {
var ls = lines(corpus);
var total = 0;
for (var i = 0, n = ls.length; i < n; i++) {
total += wcLine(ls[i], search);
}
return total;
}
Поиск в строке включает в себя разбиение на слова и сравнение каждого слова с искомым:
function wcLine(line, search) {
var words = line.split(' ');
var total = 0;
for (var i = 0, n = words.length; i < n; i++) {
if (matches(words[i], search)) {
total++;
}
}
return total;
}
Оставшие за кадром детали можно посмотреть в этом коде [7], он маленький и автономный (без зависимостей)
На моем ноуте код отрабатывает по всем пьесам Шекспира за 280-290ms. Не так уж и плохо, но как говорится, есть к чему стремиться.
Одно из самых замечательных свойств Rust-а заключается в том что крайне эффективный код может быть удивительно компактным и читаемым. В Rust-овской версии [8] код подсчета вхождений для строк выглядит почти так же как JS код:
let mut total = 0;
for word in line.split(' ') {
if matches(word, search) {
total += 1;
}
}
total // в Rust можно опустить `return` при возврате значения
На самом деле такой код можно написать с более высокоуровневыми абстракциями без потери производительности используя итерационные (перебирающие) методы как filter
и fold
(аналоги Array.prototype.filter
и Array.prototype.reduce
в JS):
line.split(' ')
.filter(|word| matches(word, search))
.fold(0, |sum, _| sum + 1)
Мои эксперименты (на скорую руку) показали даже незначительный (на пару миллисекунд) прирост производительности. Мне кажется это прекрасная демонстрация Rust-овской парадигмы абстракций с нулевой стоимостью, где высокоуровневые абстракции дают в итоге сравнимый или даже превосходящий по производительности (за счет дополнительных возможностей для оптимизации, например отказ от проверок границ) код, чем низкоуровневый и более запутанный.
На моей машинке Rust-овская версия отрабатывает за 80-85ms. Неплохо, трехкратный рост только за счет использования Rust-a, причем примерно с таким же объемом кода (60 строк в JS, 70 — Rust). И кстати, я тут сильно округляю числа — это ведь не rocket scince, я всего лишь хочу показать что вы можете получить значительное повышение производительности используя Rust, но все зависит от ситуации.
Но это еще не все! Rust позволяет нам сделать кое-что поинтереснее для Node: мы можем легко и непринужденно распараллелить наш код, причем без ночных кошмаров и холодного пота, вызванного многопоточностью [9]. Давайте взглянем на реализацию этого на Rust:
let total = vm::lock(buffer, |data| {
let corpus = data.as_str().unwrap();
let lines = lines(corpus);
lines.into_iter()
.map(|line| wc_line(line, search))
.fold(0, |sum, line| sum + line)
});
vm::lock
API дает Rust-тредам безопасный доступ к Node-объекту Buffer
(то есть к строго типизованному массиву), блокируя при этом исполнение JS кода.
Что бы продемонстрировать, насколько это легко я использовал новый Rayon от Niko Matsakis [10] — набор прекрасных абстракций для параллельной обработки данных. Изменения в коде минимальны — просто меняем цепочку into_iter/map/fold/
на это:
lines.into_par_iter()
.map(|line| wc_line(line, search))
.sum()
Обратите внимание — Rayon не разрабатывался специально для Neon, просто Rayon реализует протокол итераторов Rust, поэтому Neon может использовать его из коробки.
С этими небольшими изменениями мой двухядерный MacBook Air выполняет демку за 50ms вместо 85ms на предыдущей версии.
Я постарался сделать интеграцию настолько гладкой, насколько это возможно. Со стороны Rust, функции Neon следуют простому протоколу, получают Call
объект и возвращают JavaScript значение:
fn search(call: Call) -> JS<Integer> {
let scope = call.scope;
// ...
Ok(Integer::new(scope, total))
}
Объект scope
безопасно отслеживает хандлы (Handle) в V8 heap-е. Neon API использует систему типов Rust — это дает гарантию что ваш модуль не уронит приложение неправильным управлением Handles объектов (тут ковыряются в кишках Node [11], заодно и Handle используют, можно посмотреть… 2009-ый, сейчас на Хабре так уже не пишут...).
Со стороны JS загрузка модуля проста до безобразия:
var myNeonModule = require('neon-bridge').load();
надеюсь этого демо будет достаточно что бы заинтересовать вас. Помимо фана, я думаю быстродействие и параллельность — сильные аргументы за использование Rust в Node. Так как экосистема Rust растет, это может стать неплохой возможностью получить доступ Node к либам Rust. Как следствие, я надеюсь Neon сможет стать хорошим уровнем абстракции который сделает процесс написания расширений для Node менее болезненным. С проектами вроде node-uwp [12] может быть даже стоит исследовать развитие Neon в сторону уровня абстракции над JS-engine (что бы это ни значило).
В общем тут море возможностей, но… мне нужна помощь [3]! Для тех кто хочет поучаствовать — я создал чатик в Slack для community [13], инвайт можно получить тут [14], а также IRC канал #neon
на Mozilla IRC [15](irc.mozilla.org
).
Тут много в чем еще разбираться и тонны недоделанной работы но и то что сделано было бы невозможно без помощи: Andrew Oppenlander’s blog post [16] дал мне нащупать почву под ногами, Ben Noordhuis и Marcin Cieślak научили готовить V8, я утащил пару приемов из злодейски гениального кода [17] написанного Nathan Rajlich; Adam Klein и Fedor Indutny помогли понять V8 API, Alex Crichton помог мне с таинством компиляции и линковки, Niko Matsakis помог с дизайном API безопасного управления памятью, а Yehuda Katz помог со всем остальным дизайном.
Если вы хоть что-то поняли из сказанного — возможно вы тоже можете помочь [3]!
Автор: gearbox
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/112616
Ссылки в тексте:
[1] Neon: Node + Rust: http://calculist.org/blog/2015/12/23/neon-node-rust/
[2] бесстрашного программирования: http://blog.rust-lang.org/2015/08/14/Next-year.html
[3] Neon: https://github.com/dherman/neon
[4] Neon-cli: https://github.com/dherman/neon-cli
[5] скринкаст: https://raw.githubusercontent.com/dherman/neon-cli/master/screencast.gif
[6] демо: https://github.com/dherman/wc-demo
[7] этом коде: https://github.com/dherman/wc-demo/blob/master/lib/search.js
[8] Rust-овской версии: https://github.com/dherman/wc-demo/blob/master/src/lib.rs
[9] без ночных кошмаров и холодного пота, вызванного многопоточностью: http://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html
[10] новый Rayon от Niko Matsakis: http://smallcultfollowing.com/babysteps/blog/2015/12/18/rayon-data-parallelism-in-rust/
[11] ковыряются в кишках Node: https://habrahabr.ru/post/72592/
[12] node-uwp: https://blogs.windows.com/buildingapps/2015/05/12/bringing-node-js-to-windows-10-iot-core/
[13] чатик в Slack для community: http://rustbridge.slack.com/
[14] тут: http://rustbridge-community-slackin.herokuapp.com/
[15] Mozilla IRC: https://wiki.mozilla.org/IRC
[16] Andrew Oppenlander’s blog post : http://oppenlander.me/articles/rust-ffi
[17] злодейски гениального кода : https://github.com/TooTallNate/node-bindings/blob/master/bindings.js
[18] Источник: https://habrahabr.ru/post/277453/
Нажмите здесь для печати.