Как Node.js изначально быстрее, когда он по-прежнему полагается на потоки внутри?

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

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

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

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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

Чтобы подделать неблокирующий IO, необходимы потоки: блокировать IO в отдельном потоке. Это уродливое решение и вызывает большие накладные расходы.

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

  • С DMA процессор асинхронно выгружает IO.
  • Данные передаются непосредственно между устройством ввода-вывода и памятью.
  • Ядро обертывает это в синхронный, блокирующий системный вызов.
  • Node.js завершает системный вызов блокировки в потоке.

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

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

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

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

Cheers, Toby

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

Я ничего не знаю о внутренних функциях 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 запросов в одном обращении к базе данных.