Каков ответ Haskell на Node.js?

217

Я полагаю, что сообщество Erlang не завидует Node.js, поскольку оно изначально не блокирует ввод-вывод и имеет способы легко масштабировать развертывания на более чем один процессор (что даже не встроено в Node.js). Более подробная информация на http://journal.dedasys.com/2010/04/29/erlang-vs-node-js и Node.js или Erlang

Что насчет Хаскелла? Может ли Haskell обеспечить некоторые преимущества Node.js, а именно чистое решение, позволяющее избежать блокировки ввода-вывода без использования многопоточного программирования?


Есть много вещей, которые привлекательны с Node.js

  1. События: нет манипуляций с потоками, программист предоставляет только обратные вызовы (как в Snap Framework)
  2. Обратные вызовы гарантированно выполняются в одном потоке: условия гонки невозможны.
  3. Хороший и простой UNIX-дружественный API. Бонус: отличная поддержка HTTP. DNS также доступен.
  4. Каждый ввод / вывод по умолчанию является асинхронным. Это позволяет избежать замков. Однако слишком большая загрузка ЦП в обратном вызове повлияет на другие соединения (в этом случае задача должна быть разбита на более мелкие подзадачи и перепланирована).
  5. Один и тот же язык для клиентской и серверной сторон. (Однако я не вижу в этом особой ценности. JQuery и Node.js разделяют модель программирования событий, но остальное сильно отличается. Я просто не вижу, как совместное использование кода между серверной и клиентской сторонами могло бы быть полезным на практике.)
  6. Все это упаковано в один продукт.
gawi
источник
17
Я думаю, что вы должны задать этот вопрос вместо программистов .
Джонас
47
Отсутствие фрагмента кода не делает его субъективным вопросом.
Гави
20
Я не знаю много о node.js, но одна вещь поразила меня в вашем вопросе: почему вы находите перспективу потоков столь неприятной? Потоки должны быть точно правильным решением для мультиплексирования ввода / вывода. Здесь я широко использую термин «потоки», включая процессы Эрланга. Возможно, вы беспокоитесь о замках и изменчивом состоянии? Вам не нужно делать такие вещи - использовать передачу сообщений или транзакции, если это имеет больше смысла для вашего приложения.
Саймон Марлоу
9
@gawi Не думаю, что это звучит очень просто для программирования - без упреждения вам придется иметь дело с возможностью голодания и длительных задержек. По сути, потоки - это правильная абстракция для веб-сервера - нет необходимости иметь дело с асинхронным вводом-выводом и всеми сопутствующими трудностями, просто делайте это в потоке. Кстати, я написал статью о веб-серверах в Haskell, которая может показаться вам интересной: haskell.org/~simonmar/papers/web-server-jfp.pdf
Саймон Марлоу,
3
«Обратные вызовы гарантированно выполняются в одном потоке: условия гонки невозможны». Неправильно. Вы можете легко иметь условия гонки в Node.js; просто предположим, что одно действие ввода-вывода будет завершено раньше, чем другое, и BOOM. Что это действительно невозможно один конкретный вид условий гонки, а именно одновременно unsynchronised доступ к тому же байт в памяти.
праву,

Ответы:

219

Итак, посмотрев небольшую презентацию node.js , на которую указал мне @gawi, я могу сказать немного больше о том, как Haskell сравнивается с node.js. В презентации Райан описывает некоторые преимущества Green Threads, но затем продолжает говорить, что не считает недостатком абстракции потоков недостаток. Я бы не согласился с его позицией, особенно в контексте Haskell: я думаю, что абстракции, которые предоставляют потоки, важны для того, чтобы сделать серверный код более простым и правильным и более надежным. В частности:

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

  • обратные вызовы в одном потоке ОС - это совместная многозадачность, в отличие от вытесняющей многозадачности, которую вы получаете с потоками. Основным недостатком совместной многозадачности является то, что программист несет ответственность за отсутствие голода. Он теряет модульность: ошибиться в одном месте, и это может испортить всю систему. Это действительно то, о чем вам не нужно беспокоиться, и упреждение - это простое решение. Более того, связь между обратными вызовами невозможна (это может привести к тупику).

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

Саймон Марлоу
источник
42
Итак, я понимаю, что node.js является решением 2 проблем: 1 - параллелизм сложен в большинстве языков, 2 - использование потоков ОС является дорогостоящим. Решение Node.js заключается в использовании параллелизма на основе событий (w / libev), чтобы избежать связи между потоками и избежать проблем масштабируемости потоков ОС. У Haskell нет проблемы # 1 из-за чистоты. Для # 2 у Haskell есть облегченные потоки + менеджер событий, который был недавно оптимизирован в GHC для крупномасштабных контекстов. Кроме того, использование Javascript просто не может восприниматься как плюс для любого разработчика на Haskell. Для некоторых людей, использующих Snap Framework, Node.js «просто плох».
Гави
4
Обработка запросов в большинстве случаев представляет собой последовательность взаимозависимых операций. Я склонен согласиться с тем, что использование обратных вызовов для каждой операции блокировки может быть громоздким. Потоки лучше подходят для этого, чем обратный вызов.
Гави
10
Ага! А совершенно новое мультиплексирование ввода / вывода в GHC 7 делает пишущие серверы на Haskell еще лучше.
andreypopp
3
Ваш первый пункт не имеет большого смысла для меня (как постороннего) ... При обработке запроса в node.js ваш обратный вызов имеет дело с одним клиентом. Управление состоянием становится проблемой только при масштабировании на несколько процессов, и даже в этом случае довольно просто использовать доступные библиотеки.
Рикардо Томази
12
Это не отдельная проблема. Если этот вопрос является подлинным поиском лучших инструментов для работы в Haskell или проверкой того, существуют ли в Haskell превосходные инструменты для работы, то неявное предположение о том, что многопоточное программирование было бы неподходящим, должно быть подвергнуто сомнению, поскольку Haskell делает нити довольно разные, как отмечает Дон Стюарт. Ответы, объясняющие, почему сообщество Haskell также не ревнует к Node.js, очень интересны для этого вопроса. Ответ Гави предполагает, что это был правильный ответ на его вопрос.
AndrewC
154

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

Да, на самом деле события и потоки объединены в Haskell.

  • Вы можете программировать в явных легких потоках (например, миллионы потоков на одном ноутбуке).
  • Или; Вы можете программировать в асинхронном управляемом событиями стиле, основанном на масштабируемом уведомлении о событиях.

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

Например, для

Параллельные коллекции nbody на 32 ядра

альтернативный текст

В Haskell у вас есть как события, так и потоки, а так же все события под капотом.

Прочитайте статью с описанием реализации.

Дон стюарт
источник
2
Спасибо. Мне нужно переварить все это ... Кажется, это специфично для GHC. Я думаю, все в порядке. Язык Haskell - это когда-нибудь все, что может компилировать GHC. Аналогичным образом, «платформа» Haskell является более или менее средой выполнения GHC.
Гави
1
@gawi: Это и все другие пакеты, которые входят в комплект, так что он полезен прямо из коробки. И это то же изображение, которое я видел в моем курсе CS; и самое приятное то, что в Haskell нетрудно добиться подобных потрясающих результатов в ваших собственных программах.
Роберт Массайоли
1
Привет, Дон, как ты думаешь, ты мог бы связаться с веб-сервером haskell, который работает лучше всего (Warp), отвечая на подобные вопросы? Вот довольно актуальный тест для Node.js: yesodweb.com/blog/2011/03/…
Грег Вебер,
4
Просто в теории. «Легкие нити» на Haskell не так легки, как вы думаете. Зарегистрировать обратный вызов на интерфейсе epoll намного дешевле, чем планировать так называемые зеленые потоки, они, конечно, дешевле, чем потоки ОС, но они не бесплатны. Создание 100.000 из них использует ок. 350 МБ памяти и займет некоторое время. Попробуйте 100.000 соединений с node.js. Совершенно никаких проблем . Это было бы волшебно, если бы не было быстрее, поскольку ghc использует epoll под капотом, поэтому они не могут быть быстрее, чем непосредственное использование epoll. Программирование с интерфейсом потоков довольно приятно, хотя.
Kr0e
3
Кроме того: Новый диспетчер ввода-вывода (ghc) использует алгоритм планирования, который имеет (m log n) сложность (где m - количество выполняемых потоков, а n - общее количество потоков). Epoll имеет сложность k (k - число читаемых / записываемых fd's =. Таким образом, ghc имеет O (k * m log n) по всей сложности, что не очень хорошо, если вы сталкиваетесь с соединениями с большим трафиком. Node.js имеет только линейную сложность, вызванную И давайте не будем говорить о производительности Windows ... Node.js намного быстрее, потому что он использует IOCP
Kr0e
20

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

События: нет манипуляций с потоками, программист предоставляет только обратные вызовы (как в Snap Framework)

Таким образом, имея это в виду, вы можете писать с использованием асинхронного стиля, если вы того пожелаете, но при этом вы упустите запись в эффективном синхронном стиле с одним потоком на запрос. Haskell невероятно эффективен в синхронном коде, особенно по сравнению с другими языками. Это все события внизу.

Обратные вызовы гарантированно выполняются в одном потоке: условия гонки невозможны.

Вы можете по-прежнему иметь состояние гонки в node.js, но это сложнее.

Каждый запрос находится в своей собственной теме. Когда вы пишете код, который должен взаимодействовать с другими потоками, очень просто сделать его потокобезопасным благодаря примитивам параллелизма haskell.

Хороший и простой UNIX-дружественный API. Бонус: отличная поддержка HTTP. DNS также доступен.

Взгляните на hackage и убедитесь сами.

Каждый ввод / вывод по умолчанию является асинхронным (хотя иногда это может раздражать). Это позволяет избежать замков. Однако слишком большая загрузка ЦП в обратном вызове повлияет на другие соединения (в этом случае задача должна быть разбита на более мелкие подзадачи и перепланирована).

У вас нет таких проблем, GHC будет распределять вашу работу среди реальных потоков ОС.

Один и тот же язык для клиентской и серверной сторон. (Однако я не вижу в этом особого смысла. JQuery и Node.js разделяют модель программирования событий, но остальное сильно отличается. Я просто не вижу, как совместное использование кода на стороне сервера и на стороне клиента может быть полезным на практике.)

Хаскелл не может выиграть здесь ... верно? Подумайте еще раз, http://www.haskell.org/haskellwiki/Haskell_in_web_browser .

Все это упаковано в один продукт.

Скачать GHC, запустить клику. Есть пакет для каждой потребности.

dan_waterworth
источник
Я просто играл адвоката дьявола. Так что да, я согласен с вашими мнениями. За исключением клиентской и серверной языковой унификации. Хотя я думаю, что это технически выполнимо, я не думаю, что это может в конечном итоге заменить всю имеющуюся сегодня экосистему Javascript (JQuery и друзья). Хотя это аргумент, выдвигаемый сторонниками Node.js, я не думаю, что это очень важный аргумент. Вы действительно должны разделить столько кода между уровнем презентации и вашим бэкэндом? Действительно ли мы хотим, чтобы программисты знали только один язык?
Гави
Реальный выигрыш в том, что вы можете рендерить страницы как на стороне сервера, так и на стороне клиента, что упрощает создание страниц в реальном времени.
dan_waterworth
@dan_waterworth точно, смотрите метеор или derby.js
mb21
1
@gawi У нас есть производственные сервисы, где 85% кода распределяется между клиентом и сервером. Это известно как универсальный JavaScript в сообществе. Мы используем React для динамического рендеринга контента на сервере, чтобы сократить время до первого полезного рендеринга на клиенте. Хотя я знаю, что вы можете запускать Haskell в браузере, я не знаю ни одного набора "универсальных лучших практик" Haskell, которые допускают рендеринг на стороне сервера и на стороне клиента с использованием одной и той же кодовой базы.
Эрик Эллиот
8

Я лично вижу Node.js и программирование с обратными вызовами как излишне низкоуровневую и немного неестественную вещь. Зачем программировать с обратными вызовами, если хорошая среда выполнения, такая как в GHC, может обрабатывать обратные вызовы и делать это довольно эффективно?

Между тем, время выполнения GHC значительно улучшилось: теперь в нем есть «новый новый менеджер ввода-вывода» под названием MIO, где «M» означает многоядерный, как мне кажется. Он основан на существующем диспетчере ввода-вывода, и его главная цель - устранить причину снижения производительности ядра на 4+. Показатели производительности, представленные в этой статье, впечатляют. Увидеть себя:

Благодаря реалистичным HTTP-серверам Mio в Haskell можно масштабировать до 20 процессорных ядер, достигая максимальной производительности в 6,5 раз по сравнению с теми же серверами, использующими предыдущие версии GHC. Задержка серверов Haskell также улучшена: [...] при умеренной нагрузке уменьшает ожидаемое время отклика в 5,7 раза по сравнению с предыдущими версиями GHC

И:

Мы также показываем, что с Mio McNettle (контроллер SDN, написанный на Haskell) может эффективно масштабироваться до 40+ ядер, достигать полной производительности более 20 миллионов новых запросов в секунду на одной машине и, следовательно, стать самым быстрым из всех существующих контроллеров SDN ,

Mio сделал это в выпуске GHC 7.8.1. Я лично вижу в этом важный шаг вперед в исполнении Haskell. Было бы очень интересно сравнить производительность существующих веб-приложений, скомпилированных с предыдущей версией GHC и 7.8.1.

vlprans
источник
6

ИМХО события хорошие, а программирование с помощью обратных вызовов - нет.

Большинство проблем, которые делают особенным кодирование и отладку веб-приложений, связано с тем, что делает их масштабируемыми и гибкими. Самое главное, природа HTTP без сохранения состояния. Это улучшает навигацию, но это навязывает инверсию управления, когда элемент IO (в данном случае веб-сервер) вызывает разные обработчики в коде приложения. Эта модель событий, или, точнее говоря, модель обратного вызова, является кошмаром, поскольку обратные вызовы не разделяют переменные области действия, и интуитивное представление о навигации теряется. Среди других проблем очень трудно предотвратить все возможные изменения состояния, когда пользователь перемещается назад и вперед.

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

Существуют последовательные структуры, такие как ocsigen (ocaml) seaside (smalltalk) WASH (прекращено, Haskell) и mflow (Haskell), которые решают проблему управления состоянием, сохраняя при этом удобство навигации и полноту REST. в этих рамках программист может выразить навигацию как обязательную последовательность, когда программа отправляет страницы и ожидает ответов в одном потоке, переменные находятся в области видимости, а кнопка «назад» работает автоматически. По своей сути это приводит к созданию более короткого, более безопасного и более читаемого кода, где навигация четко видна программисту. (честное предупреждение: я разработчик mflow)

агокороны
источник
В node.js обратные вызовы используются для обработки асинхронного ввода-вывода, например, для баз данных. Вы говорите о чем-то другом, что, хотя и интересно, не отвечает на вопрос.
Робин Грин
Ты прав. Потребовалось три года, чтобы получить ответ, который, я надеюсь,
оправдает
Теперь Node поддерживает асинхронные функции, что означает, что вы можете писать код в императивном стиле, который на самом деле является асинхронным. Он использует обещания под капотом.
Эрик Эллиот
5

Вопрос довольно нелепый, потому что 1) Хаскелл уже решил эту проблему гораздо лучше и 2) примерно так же, как Эрланг. Вот эталонный тест по отношению к узлу: http://www.yesodweb.com/blog/2011/03/preditional-warp-cross-language-benchmarks

Дайте Haskell 4 ядра, и он может выполнять 100 тыс. (Простых) запросов в секунду в одном приложении. Node не может делать столько же, и не может масштабировать одно приложение по ядрам. И вам не нужно ничего делать, чтобы пожинать это, потому что среда исполнения Haskell не блокируется. Единственный другой (относительно распространенный) язык, который имеет неблокирующий ввод-вывод, встроенный во время выполнения, - это Erlang.

Грег Вебер
источник
14
Смешной? Вопрос не в том, «есть ли у Haskell ответ», а в том, «каков ответ на Haskell». В то время, когда был задан вопрос, GHC 7 даже не был выпущен, поэтому Haskell еще не был «в игре» (за исключением, может быть, фреймворков, использующих libev, таких как Snap). Кроме этого, я согласен.
Гави
1
Я не знаю, было ли это правдой, когда вы опубликовали этот ответ, но теперь, на самом деле, есть модули узлов, которые позволяют приложениям узлов легко масштабироваться по ядрам. Кроме того, эта ссылка сравнивает node.js, работающий на одном ядре, с haskell, работающим на 4 ядрах. Я бы хотел, чтобы он снова работал в более справедливой конфигурации, но, увы, репозиторий github исчез.
Тим Готье
2
Использование Haskell более 4-х ядер ухудшает производительность приложения. Была статья по этому вопросу, над которой активно работали, но она все еще остается проблемой. Поэтому запуск 16 экземпляров Node.js на 16 основных серверах, скорее всего, будет намного лучше, чем в одном приложении ghc, использующем + RTS -N16, который действительно будет медленнее, чем + RTS -N1, из-за этой ошибки во время выполнения. Это потому, что они используют только один IOManager, который замедляется при использовании со многими потоками ОС. Я надеюсь, что они исправят эту ошибку, но она существует с тех пор, поэтому у меня не было большой надежды ...
Kr0e
Любой, кто посмотрит на этот ответ, должен знать, что Node может легко обрабатывать 100 000 простых запросов на одно ядро, и тривиально легко масштабировать приложение Node без состояния по многим ядрам. pm2 -i max path/to/app.jsавтоматически масштабируется до оптимального количества экземпляров на основе доступных ядер. Кроме того, по умолчанию Node также не блокируется.
Эрик Эллиот
1

Так же , как nodejs упал libev Привязать Haskell Web Framework упал libev тоже.

Chawathe Vipul S
источник
1
Как это отвечает на вопрос?
Dfeuer
1
@dfeuer Ссылка должна читаться как, Snap Haskell Web Framework потерял libev, я не знаю, почему форматирование не работает. Время запуска сервера узла было связано с Linux libev, когда он начался, как и Snap Web FrameWork. Haskell с Snap похож на ECMAscript с nodejs, поэтому то, как Snap развивается вместе с nodejs, более важно, чем Haskell, что в этом контексте может быть более справедливым по сравнению с ECMAscript.
Chawathe Vipul S