Node v0.8.0

Автор оригинала: Isaac Z. Schlueter
  • Перевод
Я необыкновенно рад объявить о выходе новой стабильной версии Node.js.

По сравнению с выпусками Node версий 0.6 новый выпуск обеспечит существенное улучшение многих ключевых показателей производительности, а также более ясные встроенные API и появление новых средств для отладки.

Вкратце


С приходом версии 0.8.0:

  1. Node стал гораздо быстрее.
     
  2. Node стал стабильнее.
     
  3. Снова можно работать с дескрипторами файлов.
     
  4. Модуль cluster стал куда более умопотрясающим.
     
  5. Был добавлен модуль domain.
     
  6. Модуль repl стал лучше.
     
  7. Система сборки waf заменилась на gyp.
     
  8. Также случились некоторые другие изменения.
     
  9. Внизу блогозаписи — ссылки на установочные файлы Node.

Быстродействие


В этой версии были внедрены немногие ключевые изменения в V8 и libuv, вызвавшие существенное улучшение производительности.

Все нижеследующие измерения производились на моём лаптопе с OS X, однако результаты типичны и по отношению к наблюдениям под SmartOS, Linux и Windows.

# io.js

# 0.6.19, запись
Запись 1024 байтовых буферов: 19,428793471925395 МБ/сек
Запись 4096 байтовых буферов: 59,737156511350065 МБ/сек
Запись 16384 байтовых буферов: 83,97010664203543 МБ/сек
Запись 65536 байтовых буферов: 97,4184120798831 МБ/сек

# 0.8.0, запись
Запись 1024 байтовых буферов: 61,236987140232706 МБ/сек
Запись 4096 байтовых буферов: 109,05125408942203 МБ/сек
Запись 16384 байтовых буферов: 182,18254691200585 МБ/сек
Запись 65536 байтовых буферов: 181,91740949608877 МБ/сек

# v0.6.19, чтение
Чтение 1024 байтовых буферов: 29,96883241428914 МБ/сек
Чтение 4096 байтовых буферов: 62,34413965087282 МБ/сек
Чтение 16384 байтовых буферов: 165,7550140891762 МБ/сек
Чтение 65536 байтовых буферов: 266,73779674579885 МБ/сек

# v0.8.0, чтение
Чтение 1024 байтовых буферов: 57,63688760806916 МБ/сек
Чтение 4096 байтовых буферов: 136,7801942278758 МБ/сек
Чтение 16384 байтовых буферов: 244,8579823702253 МБ/сек
Чтение 65536 байтовых буферов: 302,2974607013301 МБ/сек

Разница немалая. Если вы пишете сетевые программы при помощи Node и гоняете значительный траффик, то заметите это улучшение.

Скорость чтения файлов также стала заметно быстрее:

# v0.6.19
файл прочитан 110948 раз (больше — лучше)
90141,32 наносекунд на 1 чтение (меньше — лучше)
11093,69 чтений в секунду (больше — лучше)

# v0.8.0
файл прочитан 158193 раз (больше — лучше)
63217,16 наносекунд на 1 чтение (меньше — лучше)
15818,48 чтений в секунду (больше — лучше)

И, конечно, результаты вездесущего теста HTTP-сервера «hello, world» показывают заметно более высокую скорость, особенно при крупном размере сообщения:

$ TYPE=bytes LENGTH=123 bash benchmark/http.sh  2>&1 | grep Запр
# 0.6.19
Запросов в секунду:    3317,24  (в среднем)
# 0.8.0
Запросов в секунду:    3795,34  (в среднем)


$ TYPE=bytes LENGTH=1024 bash benchmark/http.sh  2>&1 | grep Запр
# v0.6.19
Запросов в секунду:    3258,42  (в среднем)
# 0.8.0
Запросов в секунду:    3585,62  (в среднем)


$ TYPE=bytes LENGTH=123456 bash benchmark/http.sh  2>&1 | grep Запр
# v0.6.19
Запросов в секунду:    218,51  (в среднем)
# 0.8.0
Запросов в секунду:    749,17  (в среднем)

Разница в случае с откликом в Unicode ещё заметнее:

$ TYPE=unicode LENGTH=1024 bash benchmark/http.sh  2>&1 | grep Запр
# v0.6.19
Запросов в секунду:    3228,23  (в среднем)
# v0.8.0
Запросов в секунду:    3317,60  (в среднем)

$ TYPE=unicode LENGTH=12345 bash benchmark/http.sh  2>&1 | grep Запр
# v0.6.19
Запросов в секунду:    1703,96  (в среднем)
# v0.8.0
Запросов в секунду:    2431,61  (в среднем)

$ TYPE=unicode LENGTH=55555 bash benchmark/http.sh  2>&1 | grep Запр
#v0.6.19
Запросов в секунду:    161,65  (в среднем)
#v0.8.0
Запросов в секунду:    980,38  (в среднем)

$ TYPE=unicode LENGTH=99999 bash benchmark/http.sh  2>&1 | grep Запр
# v0.6.19
^C  # после нескольких часов потерял терпение
# v0.8.0
Запросов в секунду:    252,69  (в среднем)

Чем больше передаёте байтов, чем больше делаете работы, тем заметнее для вас будет превосходство Node версии 0.8 над 0.6.

Большей частью рост производительности происходит от улучшений в V8. Разработчики V8 были весьма отзывчивы по отношению к нуждам проекта Node.js. Большей частью успех Node происходит от возможности воздвигнуть Node поверх такой выдающейся VM.

Система сборки


С самого начала в Node использовалась система сборки WAF, которая основана на Python и похожа на SCons. Проект Chrome недавно перешёл со SCons на систему метасборки GYP. Она создаёт мэйкфайлы, или файлы проектов Visual Studio, или файлы XCode — в зависимости от системы, для которой происходит сборка. Будучи частью проекта Chrome, V8 теперь собирается посредством GYP. Используя GYP, Node получает возможность

  • интеграции с оптимальною системою сборки на каждой из платформ,
     
  • лёгкость интеграции процесса сборки V8 в сборку всего Node,
     
  • декларативное описание компиляции, что улучшает управляемость.

В Node v0.6 ужé использовался GYP для сборки под Windows, но теперь в GYP определяется сборка подо все платформы. Внешние дополнительные модули Node ещё только начали переход на GYP, и модуль node-gyp прилагается к npm. В будущих выпусках node-waf официально будет объявлен нерекомендуемым. Если сейчас ваш модуль использует wscript, пожалуйста, перейдите на gyp как можно скорее.

Рост стабильности


Переход от libev и libeio к libuv в версии 0.6 оказался несколько дестабилизирующим для многих внутренних частей Node. Был повод принести эту жертву: libuv — очевидный выбор кросс-платформенной библиотеки асинхронного ввода-вывода, и производительность Node.js впечатляет и под Windows, и под Unix. Однако переход от версии 0.4 к 0.6 оказался для многих пользователей далеко не гладким: библиотека libuv по возрасту уступала Node, что сказывалось на ранних выпусках.

Но в настоящее время, за очень немногими исключениями, если ваша программа для v0.6 не идёт на v0.8, то в неё легко внести все необходимые изменения очевидным способом. Libuv существенно подразвилася, и в итоге Node 0.8 выигрывает в простоте и эффективности.

Подробности изменений в отдельных API смотрите в вики-статье о переходе на новую версию.

Возвращение файловых дескрипторов


В версии Node 0.4 был метод listenFD, при помощи которого серверы могли «слушать» конкретный файловый дескриптор, ужé привязанный к некоторому сокету или порту. В версии 0.6 эту функциональность убрали — в основном потому, что она была сильно завязана на Unix, так что её нельзя было легко перенести на новую кросс-платформенную основу libuv.

Но так как наиболее часто к listenFD прибегали как к методу, позволявшему серверам из нескольких процессов Node обращаться к одному и тому же нижележащему ресурсу, то для этой цели добавили модуль cluster. Тем не менее, ряд других приёмов употребления listenFD остался ни с чем, и вот почему некоторым программистам использовать Node 0.6 не удавалось.

В версии 0.8 прежнюю функциональность обеспечит новый параметр server.listen({ fd: number }).

Ещё одной возможностью из Node 0.4, не попавшею в 0.6, была возможность передачи произвольных файловых дескрипторов в качестве stdio дочернему процессу при помощи массива customFds. В версии Node 0.6 массив customFds можно использовать для наследования stdio родительского процесса, но не для передачи произвольных хэндлов или файловых дескрипторов в дочерний stdio. Также никогда не было и способа для передачи чего-то большего, нежели стандартная троица in, out, err, так что программы, ожидавшие какого-то особенного открытого четвёртого файлового дескриптора, терпели в том неудачу.

В версии 0.8 мы добавили массив stdio к параметрам child_process.spawn. Передавайте сколько угодно файловых дескрипторов, хэндлов и так далее — дочерний процесс увидит их как файловые дескрипторы, притом ужé открытые.

Более мощный cluster


Модуль cluster в 0.8 настолько улучшен по сравнению с 0.6, что можно попросту назвать его заново написанным. Его API в основном сохраняет обратную совместимость, но не полностью. (Читайте подробности в вики-статье о переходе на новую версию.)

За исключением этих (очень небольших) изменений API, если вы пользовались в 0.6 кластером, то программа ваша станет работать и в 0.8, но при этом действовать побыстрее и вести себя получше. А если вы не обратите внимания на достоинства новых возможностей кластера 0.8, то напрасно вы это.

Их так много, что здесь и не перескажешь толком. Идите, почитайте документацию по API.

Домены


Изначальная идея «доменов» была в том, чтобы объединить несколько различных операций ввода-вывода для того, чтобы был какой-то контекст на случай возникновения ошибки.

С тех пор, как Райан обсуждал эту возможность с пользователями Node в рамках «Летнего лагеря NodeConf» в прошлом году, к реализации «доменов» возвращались несколько раз. Решаемая проблема более-менее понятна, однако большинство попыток решить её либо приводили к серьёзному падению производительности, либо проявлялись какие-нибудь трудности в частных случаях.

Так что к 0.8 мы пришли с весьма ограниченною версией реализации этой идеи. По умолчанию она выключена, использовать её вы можете по желанию, при использовании она мало влияет на производительность (а при неиспользовании не влияет никак). В документации по API есть масса примеров, так что прочтите их — и можете начинать улучшенную ловлю ошибок и падений программ.

Модуль domain всё ещё считается экспериментальным. Мы ждём ваших откликов, так что, пожалуйста, используйте его и дайте нам знать, что думаете о нём.

Repl, readline, TTY


Модули repl, readline и TTY были существенно подтянуты. Интерфейсы между этими тремя модулями зачистили и прорефакторили, убрав множество неприятных точек соприкосновения и упростив употребление этих модулей при отладке ваших программ.

Все эти нюансы иногда кажутся такими мелочами, но эти мелочи в repl сильно улучшают жизнь. Лично мне больше всего понравились вот какие:

  1. Если набрать fs или net или path, автоматически загрузится одноимённый модуль.
     
  2. Если набрать npm install ..., то будет выдана полезная подсказка.
     
  3. Больше не будет прежних глупостей в том случае, когда длинная строка переносится по границе экрана, а в ней ещё и Backspace нажать. Всё будет обработано правильно.

Взгляд в грядущее


Как было и с прежними чётными версиями, в семействе версий 0.8 будет поддерживаться стабильность API и ABI на протяжении всей его жизни.

В семействе версий 0.6 будут случаться новые выпуски по мере исправления критических ошибок или брешей в безопасности вплоть до конца 2012 года. Однако версия 0.6 не будет в центре внимания команды разработчиков.

Выпуски 0.9 начнутся через пару следующих недель. В центре внимания при разработке v0.9 будет вот что:

  • Реализация HTTP. Модуль http ужé существенно используется в реальном мире, но ему крайне необходима зачистка и рефакторинг. Особое внимание будет направлено на согласование интерфейсов, на рост производительности, на улучшение корректности работы в ряде частных случаев.
     
  • API потоков. Замысл API потокового доступа лежит в сáмой основе Node. Однако, как и в случае с HTTP, рост его возможностей был постепенным и корявым, так что теперь модуль придётся почистить. Сейчас трудно правильно с ним работать, особенно это касается обработки ошибок.
     
  • Потоки libuv. Интерфейсы хэндлов в libuv будут подвергнуты рефакторингу для согласования с остальной массою кода и меж платформами.

Кроме этого, в некоторых областях Node.js можно улучшить, чтобы движок обрёл внутреннюю самосогласованность, заговорил на языке JavaScript без акцента, улучшил производительность. Ни один из этих замыслов ещё не облёкся во плоть, но вот список некоторых идей на нашем горизонте планирования:

  • Следовало бы перейти от буферов к типизированным массивам. Буферы продолжат работать, но так как родными для JavaScript являются типизированные массивы, то логично двинуться в их направлении, их объявить предпочтительным API.
     
  • В настоящее время производительность SSL оставляет желать много лучшего. Интерфейс от Node к OpenSSL получился несколько безыскусным, есть немало возможностей его улучшения.
     
  • Модуль VM нуждается в существенном улучшении. В нём нет средств эмуляции джаваскриптового контекста браузера, а это неадекватно.
     
  • Модуль crypto всё ещё пользуется несколькими весьма устаревшими API. В версии 0.8 он много где принимает буферы (наконец-то!), но всё ещё не обеспечивает потокового интерфейса, как у других частей Node.

На этом этапе набор возможностей Node в значительной мере закончен. При зачистке внутри может что-нибудь изменяться, но, как вам нетрудно видеть, никаких существенных новых возможностей мы не планируем внедрять. Круг наших работ очерчен, и настало время продолжить сосредоточение на улучшении стабильности и производительности основных функций, тем поддерживая инновационность ваших программ.

И напоследок, для тех читателей, которые интересуются, что было добавлено после версии 0.7.12, обычный список изменений в этом выпуске:

25 июня 2012 года, версия 0.8.0 (стабильная)


  • V8: апгрейд до версии 3.11.10.10
     
  • npm: апгрейд до 1.1.32
     
  • iowatcher объявлен нерекомендуемым (Ben Noordhuis)
     
  • windows: обновлён значок программы (Bert Belder)
     
  • http: предупреждения «MUST NOT have a body» отправляются в debug() (isaacs)
     
  • Содержимое blog.nodejs.org помещено в репозиторий (isaacs)
     
  • Исправление №3503 в stdin: resume() в pipe(dest) (isaacs)
     
  • crypto: исправлено сообщение об ошибках в SetKey() (Fedor Indutny)
     
  • Добавлены флаги командной строки --no-deprecation и --trace-deprecation (isaacs)
     
  • fs: исправлен метод fs.watchFile() (Ben Noordhuis)
     
  • fs: исправлен метод fs.readfile() в случае pipe-обработки (isaacs)
     
  • Переименована GYP-переменная node_use_system_openssl для соответствия остальному коду (Ryan Dahl)

Комментарии 19

    +6
    Хвост первоисточника намеренно оборван при переводе: даже если бы я приводил гиперссылки и SHA-суммы установочных файлов, всё равно в целях безопасности вам следовало бы доверять одному только первоисточнику, а не то вдруг бы я подменил их.
      +5
      Сразу скажу, что повышенное быстродействие радикально проявляется не во всех частных случаях.

      Например, мой недавний скрипт для чтения заголовков фидонетовской эхопочты из базы JAM с анализом массива Subfields отрабатывает на Node 0.6.19 за ≈шесть секунд. В новой версии (на Node 0.8.0) выигрыш по времени составляет, во всяком случае, меньше одной секунды. (Точнее не могу сказать, потому что мне не хочется гонять его десятки раз и усреднять результаты.)
      • НЛО прилетело и опубликовало эту надпись здесь
          +5
          Увидел «блогозаписи» и сразу понял кто автор =)
          PS Спасибо за приведенные измерения.
            +3
            Это перевод.
            +2
            Очень радует появление worker.send(message, [sendHandle]) и доменов
              +1
              На счёт первого пункта (второго аргумента у .send) могу сказать, что меня это не только радует, но и экономит мне кучу времени в рабочем проекте. Ура!
                +1
                Напишите о впечатлениях как-нибудь? :)
                  0
                  Если выгорит, почему бы и нет? :) Просто в связке клиент-сервер-fork, где fork должен общаться с клиентом, реализация этого путём обмена сообщениями send между сервером и fork'ом адски выносила мозг.
                    0
                    Хотя, а что тут писать? Только если о своём горьком опыте проектирования. Рад, что теперь этот функционал send'а в стабильной версии.
                    У меня просто сервер крутится на node.js, и в некоторых, особых случаях, приходится «считать факториал».
                    Cluster использовать — не вариант, т.к. «факториалу» нужно только соединение с особой базой данных, для которой насписан самодельный драйвер, с другой стороны, основной сервер в require имеет адскую кучу зависимостей.
                    Приходилось fork'ать другую программку с «программой-факториалом» и общаться с основным сервером через send, чтобы он что-то отправлял клиенту. Причем сценариев отправки — море.
                    Теперь, надеюсь, станет гораздо проще.
                      +1
                      Да мне в целом интересно, кто как использует и чего с помощью Node.js уже добились :)
                +2
                речь идёт о webworker'ах?
              0
              А адекватной иерархии Error-классов и их наследования так и не появилось?
                +1
                А конкретнее? :)
                  +1
                  Ну вот это:
                  var MyError = function (code, msg) {
                      Error.call(this);
                      Error.captureStackTrace(this, this.constructor);
                      this.code = code;
                      this.name = 'My Error';
                      this.message = msg;
                  };
                  MyError.prototype = Error;
                  MyError.prototype.constructor = MyError;
                  

                  — извращение. CoffeeScript несколько улучшает дело, но не сильно.
                  Хочется что-нибудь вроде:
                  var MyError = Error.extend('My Error', function (code, message) {
                      Error.call(this, code, message);
                  });
                  

                  Это касательно наследования.

                  А с иерархией вообще беда, насколько я заметил (могу ошибаться, буду рад, если меня поправят), ошибки в разных модулях абсолютно не разнесены по типам (т.е. например ошибки в fs все — Error, отличаются только code и errno). Во-первых эти доп. поля нигде не описаны (я не нашел), во-вторых не получается писать такой код:
                  if (err instanceof NotExistsError) {
                      // handler 1
                  } else if (err instanceof MemoryError) {
                      // handler 2
                  } else {
                      // handler 3
                  }
                  
                    0
                    Хочется что-нибудь вроде:
                    var MyError = Error.extend('My Error', function (code, message) {
                        Error.call(this, code, message);
                    });

                    Ну тут уж Node.js не причём — это библиотека общего назначения для JavaScript, а фреймворк с конкретными целями. Если вы хотите избежать нескольких лишних строчек для описания наследования — возьмите Mootools.Core, например.
                    Или напишите хелпер, который был бы ещё короче: var MyError = Error.extend('My Error');

                    По поводу того, как представляются ошибки — имхо вы предлагаете слишком навороченную концепцию. Можно было бы для каждого кода ошбики сделать свой класс конечно, но чем тогда ваш код с условиями на instanceof будет отличаться от кода с условиями на err.code? Большинство кодов ошибок кроме общих вроде «error not supported» отновятся к I/O операиям и соответствуют кодам ошибок стандартной библиотеки. Всё просто и доступно, имхо.
                      +1
                      Node.js — это не просто библиотека, а (де-факто) на данный момент стандартный способ написания серверов на JS. Соответственно почти все, что в нем происходит, становится трендом (и, если повезет, общепринятой практикой).

                      Как вы будете следить за уникальностью этих кодов между разными сторонними библиотеками?
                      Большинство ошибок — это ошибки не уровня I/O операций, а бизнес логики. И они у каждой библиотеки и приложения уникальны.
                0
                Что-то у меня система квестов в разрабатываемой игре стала показывать заметно худшие результаты.
                Вчера 2 варианта бенчмарка работали за 9.1 и 4.9 секунд. Сегодня, после апдейта из launchpad.net/~chris-lea/+archive/node.js/, они стали работать 10.7 и 6.1 сек соотв-но.

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

                Самое читаемое