- PVSM.RU - https://www.pvsm.ru -

Neon: Node + Rust

Предлагаю вашему вниманию перевод статьи "Neon: Node + Rust [1]".

Javascript программистам, которых заинтриговала rust-овская тема бесстрашного программирования [2] (сделать системное [низкоуровневое] программирование безопасным и прикольным), но при этом ждущих вдохновений или волшебных пендалей — их есть у меня! Я тут поработал немного над Neon [3] — набором API и тулзов, которые делают реально легким процесс написания нативных расширений под Node на Rust.

TL;DR:

  • Neon [3] — это API для создания быстрых, надежных нативных расширений Node на Rust
  • Neon позволяет использовать параллелизм Rust-а с гарантированной потокобезопасностью
  • Neon-cli [4] позволяет легко и непринужденно создавать Neon проект и дает легкий старт… и наконец...
  • проекту требуется помощь!!!

Я научился готовить 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], так что можете сходить и убедиться.

Ловлю тебя на слове [Take Thee at thy Word]

Чтобы продемонстрировать возможности 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. Не так уж и плохо, но как говорится, есть к чему стремиться.

И сельскому веселью предадимся [ Fall Into our Rustic Revelry ]

Одно из самых замечательных свойств 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, но все зависит от ситуации.

И нить их жизни прядется [Their Thread of Life is Spun]

Но это еще не все! 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 на предыдущей версии.

Bridge Most Valiantly, with Excellent Discipline

Я постарался сделать интеграцию настолько гладкой, насколько это возможно. Со стороны 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();

Отчего этот шум? [Wherefore is this noise]

надеюсь этого демо будет достаточно что бы заинтересовать вас. Помимо фана, я думаю быстродействие и параллельность — сильные аргументы за использование 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/