Как Node.js по своей природе быстрее, когда он все еще полагается на потоки внутри? - PullRequest
276 голосов
/ 02 сентября 2010

Я только что посмотрел следующее видео: Введение в Node.js и до сих пор не понимаю, как вы получаете преимущества в скорости.

В основном, в одной точке Райан Даль (Узел.js 'creator) говорит, что Node.js основан на цикле событий, а не на потоках.Потоки дороги и должны быть оставлены на усмотрение только экспертов по параллельному программированию.

Позже он показывает архитектурный стек Node.js, который имеет базовую реализацию C и имеет собственный внутренний пул потоков.Очевидно, что разработчики Node.js никогда не запускают свои собственные потоки и не используют пул потоков напрямую ... они используют асинхронные обратные вызовы.Это то, что я понимаю.

То, что я не понимаю, это то, что Node.js по-прежнему использует потоки ... он просто скрывает реализацию, так как это быстрее, если 50 человек запрашивают 50 файлов (в настоящее время нетв памяти) ну, тогда не нужно ли 50 потоков?

Единственное отличие состоит в том, что, поскольку он управляется изнутри, разработчику Node.js не нужно кодировать многопоточные детали, но под ним он все еще использует потоки для обработки.запросы файлов ввода-вывода (блокировки).

Так разве вы не просто берете одну проблему (многопоточность) и скрываете ее, пока эта проблема все еще существует: в основном, несколько потоков, переключение контекста, мертвые блокировки ... и т. д?

Должна быть какая-то деталь, которую я до сих пор здесь не понимаю.

Ответы [ 6 ]

137 голосов
/ 03 сентября 2010

Здесь на самом деле объединяются несколько разных вещей. Но это начинается с мема, что темы просто очень сложны. Поэтому, если они сложны, вы, скорее всего, при использовании потоков 1) прерываете из-за ошибок и 2) не используете их настолько эффективно, насколько это возможно. (2) это тот, о котором вы спрашиваете.

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

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

Если поступивший запрос заставил вас создать новый поток, который выполнял приведенный выше код, у вас там будет поток, ничего не делающий, пока работает query(). (По словам Райана, Apache использует один поток для удовлетворения первоначального запроса, тогда как nginx превосходит его в тех случаях, о которых он говорит, потому что это не так.)

Теперь, если вы действительно умны, вы бы выразили приведенный выше код таким образом, чтобы среда могла работать и делать что-то еще во время выполнения запроса:

query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );

Это в основном то, что делает node.js. Вы в основном декорируете - удобным способом из-за языка и среды, а следовательно, и из-за замыканий - ваш код таким образом, что среда может быть умной в отношении того, что и когда выполняется. Таким образом, node.js не является новым в том смысле, что он изобрел асинхронный ввод-вывод (не то, чтобы кто-то утверждал что-либо подобное), но он нов в том, что способ его выражения немного отличается ,

Примечание: когда я говорю, что среда может быть умной в отношении того, что работает и когда, в частности, я имею в виду, что поток, который она использовала для запуска некоторого ввода-вывода, теперь может использоваться для обработки какого-либо другого запроса или некоторого вычисления, можно сделать параллельно или запустить какой-то другой параллельный ввод / вывод. (Я не уверен, что узел достаточно сложен, чтобы начать больше работы для того же запроса, но вы поняли.)

32 голосов
/ 07 сентября 2010

Примечание! Это старый ответ.Хотя это все еще верно в общих чертах, некоторые детали могли измениться из-за быстрого развития Node в последние несколько лет.

Он использует потоки, потому что:

  1. Опция O_NONBLOCK для open () не работает с файлами .
  2. Существуют сторонние библиотеки, которые не предлагают неблокирующий ввод-вывод.

Для имитации неблокирующего ввода-вывода необходимы потоки: блокируйте ввод-вывод в отдельном потоке.Это уродливое решение, которое приводит к значительным накладным расходам.

Еще хуже на аппаратном уровне:

  • При DMA ЦП асинхронно разгружает ввод-вывод.
  • Данные передаются непосредственно между устройством ввода-вывода и памятью.
  • Ядро заключает их в синхронный блокирующий системный вызов.
  • Node.js упаковывает блокирующий системный вызов в поток.

Это просто глупо и неэффективно.Но это работает по крайней мере!Мы можем наслаждаться Node.js, потому что он скрывает уродливые и громоздкие детали за асинхронной архитектурой, управляемой событиями.

Может быть, кто-то в будущем внедрит O_NONBLOCK для файлов? ...

Edit: Я обсуждал это с другом, и он сказал мне, что альтернативой потокам является опрос с помощью select : укажите время ожидания 0 и выполните IO для возвращенных файловых дескрипторов (теперь, когда онигарантированно не блокировать).

28 голосов
/ 27 января 2013

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

1) Комментируемый элемент в псевдокоде в одном из популярных ответов

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

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

2) «Потоки трудны», толькоимеет смысл в контексте обмена данными.Если у вас есть по существу независимые потоки, как, например, в случае обработки независимых веб-запросов, то создание потоков является тривиально простым, вы просто кодируете линейный поток обработки одной работы и сидите, зная, что она будет обрабатывать несколько запросов, и каждыйбудет эффективно независимым.Лично я бы рискнул, что для большинства программистов изучение механизма замыкания / обратного вызова является более сложным, чем простое кодирование версии потока сверху вниз.(Но да, если вам нужно общаться между потоками, жизнь становится действительно тяжелой и очень быстрой, но тогда я не уверен, что механизм закрытия / обратного вызова действительно меняет это, он просто ограничивает ваши варианты, потому что этот подход все еще достижим с потокамиВо всяком случае, это совсем другое обсуждение, которое на самом деле здесь не актуально).

3) Пока никто не представил никаких реальных доказательств того, почему один конкретный тип переключения контекста будет более или менее трудоемким, чем любойдругой тип.Мой опыт создания многозадачных ядер (в небольшом масштабе для встроенных контроллеров, ничего более необычного, чем «настоящая» ОС) подсказывает, что это не так.

4) Все иллюстрации, которые у меня естьДо сих пор мы видели, что этот термин призван показать, насколько быстрее Node, чем другие веб-серверы, ужасно испорчены, однако, они испорчены таким образом, что косвенно иллюстрируют одно преимущество, которое я определенно принял бы для Node (и оно отнюдь не незначительно).Узел не выглядит так, как будто он нуждается (и даже не разрешает) в настройке.Если у вас есть многопоточная модель, вам нужно создать достаточное количество потоков для обработки ожидаемой нагрузки.Сделайте это плохо, и вы получите плохую производительность.Если потоков слишком мало, то процессор простаивает, но не может принять больше запросов, создать слишком много потоков, и вы будете тратить впустую память ядра, а в случае среды Java вы также будете тратить впустую память основной кучи,Теперь, для Java, тратить кучу - это первый, лучший способ поднять производительность системы, потому что эффективный сбор мусора (в настоящее время это может измениться с G1, но кажется, что жюри все еще находится на этом этапе по состоянию на начало 2013 годапо крайней мере) зависит от наличия большого количества запасной кучи.Итак, есть проблема: настройте его на слишком малое количество потоков, у вас незанятые процессоры и низкая пропускная способность, настройте его на слишком много, и он застрянет другими способами.

5) Есть еще один способ, которым я принимаю логику утверждения, что подход Node «быстрее по замыслу», и это так. В большинстве потоковых моделей используется модель переключения контекста с разбивкой по времени, расположенная поверх более подходящей (оповещение о ценности :) и более эффективной (не оценочной) модели с вытеснением. Это происходит по двум причинам: во-первых, большинство программистов, по-видимому, не понимают приоритетное вытеснение, и, во-вторых, если вы изучаете многопоточность в среде Windows, существует временная зависимость, нравится вам это или нет (конечно, это усиливает первую точку Примечательно, что в первых версиях Java использовалось приоритетное вытеснение в реализациях Solaris и временная привязка в Windows. Поскольку большинство программистов не понимали и жаловались на то, что «многопоточность не работает в Solaris», они везде изменили модель на временную шкалу). В любом случае, суть в том, что временная привязка создает дополнительные (и потенциально ненужные) переключатели контекста. Каждое переключение контекста отнимает процессорное время, и это время эффективно удаляется из работы, которую можно выполнить в реальной работе под рукой. Однако время, затрачиваемое на переключение контекста из-за временного среза, не должно превышать очень небольшой процент от общего времени, если только не происходит что-то довольно странное, и я не вижу причин ожидать, что это произойдет в простой веб-сервер). Итак, да, избыточные переключатели контекста, связанные с временным разделением, неэффективны (и, как правило, в потоках kernel , как правило, кстати), но разница будет в несколько процентов пропускной способности, а не в виде факторы целого числа, которые подразумеваются в заявках на производительность, которые часто подразумеваются для узла.

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

a) реальное объяснение того, почему Node должен быть лучше (помимо двух сценариев, которые я обрисовал выше, первый из которых (плохая настройка), я считаю, является реальным объяснением всех тестов, которые я видел до сих пор. ([править], на самом деле, чем больше я об этом думаю, тем больше мне интересно, может ли здесь быть значимой память, используемая огромным количеством стеков. Размеры стеков по умолчанию для современных потоков, как правило, довольно велики, но память выделенная системой событий на основе замыканий будет только то, что нужно)

b) реальный эталонный тест, который фактически дает реальную возможность выбранному многопоточному серверу. По крайней мере, таким образом, я должен был бы перестать верить, что утверждения по сути ложные;> ([править] это, вероятно, намного сильнее, чем я предполагал, но я чувствую, что объяснения, приведенные для повышения производительности, в лучшем случае неполны, и показанные критерии являются необоснованными).

Ура, Toby

14 голосов
/ 03 сентября 2010

Что я не понимаю, так это то, что Node.js все еще использует потоки.

Райан использует потоки для тех частей, которые блокируют (большинство из node.js использует неблокирование ввода / вывода), потому что некоторые части безумно трудно писать без блокировки.Но я верю, что Райан хочет, чтобы все было неблокирующим.На slide 63 (внутренний дизайн) вы видите, что Райан использует libev (библиотека, которая абстрагирует уведомление об асинхронном событии) для неблокирующей eventloop .Из-за цикла обработки событий для node.js требуются меньшие потоки, что уменьшает переключение контекста, потребление памяти и т. Д.

11 голосов
/ 19 сентября 2010

Потоки используются только для работы с функциями, не имеющими асинхронного средства, например stat().

Функция stat() всегда блокирует, поэтому node.js должен использовать поток для выполнения фактического вызова без блокировки основного потока (цикл обработки событий).Потенциально никакой поток из пула потоков никогда не будет использован, если вам не нужно вызывать такие функции.

7 голосов
/ 02 сентября 2010

Я ничего не знаю о внутренней работе node.js, но я вижу, как использование цикла обработки событий может превзойти обработку потокового ввода-вывода.Представьте запрос диска, дайте мне staticFile.x, сделайте 100 запросов на этот файл.Каждый запрос обычно занимает поток, получающий этот файл, то есть 100 потоков.

Теперь представьте, что первый запрос создает один поток, который становится объектом публикатора, все 99 других запросов сначала проверяют наличие объекта публикатора для staticFile.xесли это так, слушайте его, пока он выполняет свою работу, в противном случае запустите новый поток и, следовательно, новый объект издателя.

Как только один поток завершен, он передает staticFile.x всем 100 слушателям и уничтожает себяТаким образом, следующий запрос создает новый новый поток и объект издателя.

Таким образом, в приведенном выше примере это 100 потоков против 1 потока, но также 1 поиск диска вместо 100 поиска диска, усиление может быть весьма феноменальным.Райан - умный парень!

Еще один способ взглянуть на это - один из его примеров в начале фильма.Вместо:

pseudo code:
result = query('select * from ...');

Опять же, 100 отдельных запросов к базе данных по сравнению с ...:

pseudo code:
query('select * from ...', function(result){
    // do stuff with result
});

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...