Jaka jest odpowiedź Haskell na Node.js?

217

Wierzę, że społeczność Erlang nie jest zazdrosna o Node.js, ponieważ natywnie blokuje operacje we / wy i ma sposoby na łatwe skalowanie wdrożeń na więcej niż jednym procesorze (coś, co nie jest nawet wbudowane w Node.js). Więcej informacji na http://journal.dedasys.com/2010/04/29/erlang-vs-node-js i Node.js lub Erlang

Co z Haskellem? Czy Haskell może zapewnić niektóre korzyści z Node.js, a mianowicie czyste rozwiązanie, aby uniknąć blokowania I / O bez konieczności programowania wielowątkowego?


Istnieje wiele rzeczy, które są atrakcyjne w Node.js

  1. Zdarzenia: Brak manipulacji wątkiem, programista zapewnia tylko wywołania zwrotne (jak w środowisku Snap)
  2. Oddzwanianie jest gwarantowane w jednym wątku: nie jest możliwy żaden wyścig.
  3. Ładny i prosty interfejs API przyjazny dla UNIX. Bonus: doskonała obsługa HTTP. DNS również dostępny.
  4. Każde wejście / wyjście jest domyślnie asynchroniczne. Ułatwia to unikanie zamków. Jednak zbyt duże przetwarzanie procesora w wywołaniu zwrotnym wpłynie na inne połączenia (w tym przypadku zadanie powinno zostać podzielone na mniejsze podzadania i ponownie zaplanowane).
  5. Ten sam język dla klienta i serwera. (Jednak nie widzę w tym zbyt dużej wartości. JQuery i Node.js współużytkują model programowania zdarzeń, ale reszta jest zupełnie inna. Po prostu nie widzę, w jaki sposób współdzielenie kodu między serwerem a klientem może przydać się w praktyce).
  6. Wszystko to zapakowane w jeden produkt.
Gawi
źródło
17
Myślę, że zamiast tego powinieneś zadać to pytanie programistom .
Jonas
47
Brak fragmentu kodu nie czyni z tego pytania subiektywnego.
gawi
20
Nie wiem wiele o node.js, ale jedno uderzyło mnie w twoim pytaniu: dlaczego uważasz, że perspektywa wątków jest tak nieprzyjemna? Wątki powinny być dokładnie właściwym rozwiązaniem dla multipleksowania I / O. Używam tutaj terminu wątki szeroko, w tym procesów Erlanga. Być może martwisz się o blokady i stan mutable? Nie musisz robić tego w ten sposób - użyj przekazywania wiadomości lub transakcji, jeśli ma to większy sens dla Twojej aplikacji.
Simon Marlow,
9
@gawi Nie wydaje mi się, żeby programowanie tego wydawało się łatwe - bez uprzedzeń trzeba poradzić sobie z możliwością głodu i długich opóźnień. Zasadniczo wątki są właściwą abstrakcją dla serwera WWW - nie ma potrzeby radzenia sobie z asynchronicznymi We / Wy i wszystkimi związanymi z tym trudnościami, po prostu zrób to w wątku. Nawiasem mówiąc, napisałem artykuł o serwerach internetowych w Haskell, który może cię zainteresować: haskell.org/~simonmar/papers/web-server-jfp.pdf
Simon Marlow
3
„Gwarantujemy, że oddzwanianie będzie uruchamiane w jednym wątku: nie jest możliwy żaden wyścig”. Źle. Możesz łatwo mieć warunki wyścigu w Node.js; wystarczy założyć, że jedno działanie I / O zakończy się przed drugim, i BOOM. Co jest rzeczywiście niemożliwe jest jeden szczególny rodzaj warunkach wyścigowych, mianowicie współbieżne niezsynchronizowane dostęp do tego samego bajta w pamięci.
prawej

Odpowiedzi:

219

Ok, po obejrzeniu trochę prezentacji node.js , na którą skierował mnie @gawi, mogę powiedzieć nieco więcej o tym, jak Haskell wypada w porównaniu z node.js. W prezentacji Ryan opisuje niektóre zalety zielonych nici, ale następnie mówi, że nie uważa braku abstrakcji nici za wadę. Nie zgadzam się z jego stanowiskiem, szczególnie w kontekście Haskella: Myślę, że abstrakcje dostarczane przez wątki są niezbędne, aby kod serwera był łatwiejszy do poprawienia i bardziej niezawodny. W szczególności:

  • użycie jednego wątku na połączenie pozwala napisać kod, który wyraża komunikację z jednym klientem, zamiast pisania kodu, który obsługuje wszystkich klientów jednocześnie. Pomyśl o tym w ten sposób: serwer, który obsługuje wielu klientów z wątkami, wygląda prawie tak samo, jak ten, który obsługuje jednego klienta; główna różnica polega na tym, że jest forkgdzieś w tym pierwszym. Jeśli implementowany protokół jest w ogóle skomplikowany, zarządzanie maszyną stanową dla wielu klientów jednocześnie staje się dość trudne, podczas gdy wątki pozwalają po prostu skryptować komunikację z jednym klientem. Kod jest łatwiejszy do poprawnego, łatwiejszy do zrozumienia i utrzymania.

  • wywołania zwrotne w jednym wątku systemu operacyjnego to wielozadaniowość kooperacyjna, w przeciwieństwie do wielozadaniowości zapobiegawczej, którą uzyskuje się dzięki wątkom. Główną wadą współpracy wielozadaniowej jest to, że programista jest odpowiedzialny za to, aby nie dopuścić do głodu. Traci modułowość: popełnij błąd w jednym miejscu i może zepsuć cały system. To naprawdę coś, o co nie musisz się martwić, a zapobieganie jest prostym rozwiązaniem. Ponadto komunikacja między wywołaniami zwrotnymi nie jest możliwa (spowodowałoby to zakleszczenie).

  • współbieżność nie jest trudna w Haskell, ponieważ większość kodu jest czysta, a więc z założenia bezpieczna dla wątków. Istnieją proste prymitywy komunikacyjne. W Haskell o wiele trudniej jest strzelać sobie w stopę niż w język o nieograniczonych skutkach ubocznych.

Simon Marlow
źródło
42
Ok, więc rozumiem, że node.js jest rozwiązaniem 2 problemów: 1 - współbieżność jest trudna w większości języków, 2 - używanie wątków systemu operacyjnego jest ekspansywne. Rozwiązaniem Node.js jest wykorzystanie współbieżności opartej na zdarzeniach (w / libev), aby uniknąć komunikacji między wątkami i uniknąć problemów ze skalowalnością wątków systemu operacyjnego. Haskell nie ma problemu nr 1 z powodu czystości. W przypadku nr 2 Haskell ma lekkie wątki + menedżera zdarzeń, który został ostatnio zoptymalizowany w GHC do kontekstów na dużą skalę. Ponadto korzystanie z Javascript nie może być postrzegane jako plus dla żadnego programisty Haskell. Dla niektórych osób korzystających ze Snap Framework Node.js jest „po prostu zły”.
gawi
4
Przetwarzanie żądań jest w większości przypadków sekwencją operacji zależnych od siebie. Zwykle zgadzam się, że używanie wywołań zwrotnych dla każdej operacji blokowania może być kłopotliwe. Wątki lepiej nadają się do tego niż wywołanie zwrotne.
gawi
10
Tak! A zupełnie nowe multipleksowanie I / O w GHC 7 czyni serwery zapisu w Haskell jeszcze lepszymi.
andreypopp
3
Twój pierwszy punkt nie ma dla mnie większego sensu (jako outsider) ... Podczas przetwarzania żądania w node.js Twoje wywołanie zwrotne dotyczy jednego klienta. Zarządzanie stanem staje się czymś, o co należy się martwić przy skalowaniu do wielu procesów, a nawet wtedy korzystanie z dostępnych bibliotek jest dość łatwe.
Ricardo Tomasi
12
To nie jest osobny problem. Jeśli to pytanie jest prawdziwym poszukiwaniem najlepszych narzędzi do pracy w Haskell lub sprawdzeniem, czy w Haskell istnieją doskonałe narzędzia do pracy, wówczas należy zakwestionować domniemane założenie, że programowanie wielowątkowe byłoby nieodpowiednie, ponieważ Haskell wątki raczej inaczej, jak zauważa Don Stewart. Odpowiedzi wyjaśniające, dlaczego społeczność Haskell również nie jest zazdrosna o Node.js, są bardzo tematyczne na to pytanie. Odpowiedź Gawiego sugeruje, że była to odpowiednia odpowiedź na jego pytanie.
AndrewC,
154

Czy Haskell może zapewnić niektóre korzyści z Node.js, a mianowicie czyste rozwiązanie, aby uniknąć blokowania I / O bez konieczności programowania wielowątkowego?

Tak, w rzeczywistości wydarzenia i wątki są zunifikowane w Haskell.

  • Możesz programować w wyraźnych wątkach (np. Miliony wątków na jednym laptopie).
  • Lub; możesz programować w stylu asynchronicznym, opartym na zdarzeniach, na podstawie skalowalnego powiadomienia o zdarzeniu.

Wątki są faktycznie implementowane pod względem zdarzeń i działają na wielu rdzeniach, z płynną migracją wątków, z udokumentowaną wydajnością i aplikacjami.

Np. Dla

Jednoczesne kolekcje nbody na 32 rdzeniach

alternatywny tekst

W Haskell masz zarówno wydarzenia, jak i wątki, a wszystko to pod maską.

Przeczytaj artykuł opisujący wdrożenie.

Don Stewart
źródło
2
Dzięki. Muszę to wszystko przetrawić ... To wydaje się być specyficzne dla GHC. Myślę, że to w porządku. Język Haskell jest czasem, gdy wszystko, co GHC może skompilować. W podobny sposób „platforma” Haskell jest mniej więcej czasem działania GHC.
gawi
1
@gawi: To i wszystkie inne pakiety, które są bezpośrednio w nim zawarte, dzięki czemu jest przydatny od razu po wyjęciu z pudełka. I to jest ten sam obraz, który widziałem na kursie CS; a najlepsze jest to, że w Haskell nie jest trudno osiągnąć podobne niesamowite wyniki we własnych programach.
Robert Massaioli,
1
Cześć Don, czy uważasz, że możesz połączyć się z serwerem internetowym haskell, który działa najlepiej (Warp), odpowiadając na takie pytania? Oto całkiem odpowiedni test porównawczy w stosunku do Node.js: yesodweb.com/blog/2011/03/…
Greg Weber
4
W teorii. „Lekkie nici” firmy Haskell nie są tak lekkie, jak myślisz. Zarejestrowanie oddzwaniania na interfejsie epoll jest znacznie dużo tańsze niż planowanie tak zwanego zielonego wątku, są one oczywiście tańsze niż wątki systemu operacyjnego, ale nie są darmowe. Utworzenie 100 000 z nich wykorzystuje ok. 350 MB pamięci i trochę czasu. Wypróbuj 100 000 połączeń z node.js. Żaden problem . Byłoby magią, gdyby nie było szybciej, ponieważ ghc używa epoll pod maską, więc nie mogą być szybsze niż bezpośrednie używanie epoll. Programowanie z interfejsem wątków jest jednak całkiem niezłe.
Kr0e
3
Ponadto: nowy menedżer we / wy (ghc) korzysta z algorytmu szeregowania, który ma (m log n) złożoność (gdzie m jest liczbą uruchomionych wątków, a n całkowitą liczbą wątków). Epoll ma złożoność k (k jest liczbą czytelnych / zapisywalnych fd =. Więc ghc ma O (k * m log n) w całej złożoności, co nie jest zbyt dobre, jeśli masz do czynienia z połączeniami o dużym natężeniu ruchu. Node.js ma tylko liniową złożoność przez epoll. I nie mówmy o wydajności systemu Windows ... Node.js jest znacznie szybszy, ponieważ używa IOCP.
Kr0e
20

Po pierwsze, nie uważam, że node.js robi właściwą rzecz, ujawniając wszystkie te wywołania zwrotne. W końcu piszesz swój program w CPS (styl przekazywania kontynuacji) i myślę, że to kompilator powinien wykonać tę transformację.

Zdarzenia: Brak manipulacji wątkiem, programista zapewnia tylko wywołania zwrotne (jak w środowisku Snap)

Mając to na uwadze, możesz pisać w stylu asynchronicznym, jeśli chcesz, ale robiąc to, stracisz możliwość pisania w efektywnym stylu synchronicznym, z jednym wątkiem na żądanie. Haskell jest absurdalnie wydajny w kodzie synchronicznym, szczególnie w porównaniu do innych języków. Wszystko pod spodem.

Oddzwanianie jest gwarantowane w jednym wątku: nie jest możliwy żaden wyścig.

Nadal możesz mieć warunki wyścigu w node.js, ale jest to trudniejsze.

Każde żądanie jest w swoim wątku. Kiedy piszesz kod, który musi komunikować się z innymi wątkami, bardzo łatwo jest zapewnić bezpieczeństwo wątków dzięki prymitywom współbieżności haskell.

Ładny i prosty interfejs API przyjazny dla UNIX. Bonus: doskonała obsługa HTTP. DNS również dostępny.

Rzuć okiem na hakerów i przekonaj się sam.

Każde wejście / wyjście jest domyślnie asynchroniczne (choć czasem może to być denerwujące). Ułatwia to unikanie zamków. Jednak zbyt duże przetwarzanie procesora w wywołaniu zwrotnym wpłynie na inne połączenia (w tym przypadku zadanie powinno zostać podzielone na mniejsze podzadania i ponownie zaplanowane).

Nie masz takich problemów, ghc rozdzieli Twoją pracę między prawdziwe wątki systemu operacyjnego.

Ten sam język dla klienta i serwera. (Jednak nie widzę w tym zbyt dużej wartości. JQuery i Node.js współużytkują model programowania zdarzeń, ale reszta jest zupełnie inna. Po prostu nie widzę, w jaki sposób współdzielenie kodu między serwerem a klientem może przydać się w praktyce).

Haskell nie może tutaj wygrać ... prawda? Pomyśl jeszcze raz, http://www.haskell.org/haskellwiki/Haskell_in_web_browser .

Wszystko to zapakowane w jeden produkt.

Pobierz ghc, odpal cabal. Istnieje pakiet na każdą potrzebę.

dan_waterworth
źródło
Po prostu grałem w adwokata diabła. Tak, zgadzam się z twoimi punktami. Z wyjątkiem unifikacji języka po stronie klienta i serwera. Chociaż myślę, że jest to technicznie wykonalne, nie sądzę, że może ostatecznie zastąpić cały ekosystem Javascript dzisiaj (JQuery i przyjaciele). Chociaż jest to argument wysunięty przez zwolenników Node.js, nie sądzę, aby był bardzo ważny. Czy naprawdę musisz udostępniać tyle kodu między warstwą prezentacji a zapleczem? Czy naprawdę chcemy, aby programiści znali tylko jeden język?
gawi
Prawdziwą zaletą jest to, że możesz renderować strony zarówno po stronie serwera, jak i klienta, co ułatwia tworzenie stron w czasie rzeczywistym.
dan_waterworth
@dan_waterworth dokładnie zobaczyć meteor lub derby.js
mb21
1
@gawi Mamy usługi produkcyjne, w których 85% kodu jest współużytkowane przez klienta i serwer. W społeczności jest to znane jako uniwersalny JavaScript. Używamy React do dynamicznego renderowania treści na serwerze, aby skrócić czas do pierwszego przydatnego renderowania w kliencie. Chociaż wiem, że możesz uruchomić Haskell w przeglądarce, nie znam żadnego zestawu najlepszych praktyk „Universal Haskell”, które pozwalają na renderowanie po stronie serwera i klienta przy użyciu tej samej bazy kodu.
Eric Elliott,
8

Osobiście uważam Node.js i programowanie z wywołaniami zwrotnymi za niepotrzebnie niskopoziomowe i trochę nienaturalne. Po co programować za pomocą wywołań zwrotnych, skoro dobry czas działania, taki jak ten w GHC, może obsługiwać wywołania zwrotne i robić to całkiem skutecznie?

W międzyczasie środowisko uruchomieniowe GHC uległo znacznej poprawie: zawiera teraz „nowego nowego menedżera IO” o nazwie MIO, w którym „M” oznacza, jak sądzę, wielordzeniowy. Opiera się na fundamencie istniejącego menedżera IO, a jego głównym celem jest przezwyciężenie przyczyny obniżenia wydajności rdzeni 4+. Liczby wydajności przedstawione w tym artykule są imponujące. Zobacz siebie:

Dzięki Mio realistyczne serwery HTTP w skali Haskell skalują się do 20 rdzeni procesora, osiągając szczytową wydajność nawet do 6,5x w porównaniu z tymi samymi serwerami używającymi poprzednich wersji GHC. Usprawniono także opóźnienie serwerów Haskell: [...] przy umiarkowanym obciążeniu skraca oczekiwany czas reakcji o 5,7x w porównaniu z poprzednimi wersjami GHC

I:

Pokazujemy również, że dzięki Mio McNettle (kontroler SDN napisany w języku Haskell) może efektywnie skalować się do ponad 40 rdzeni, osiągać przepustowość ponad 20 milionów nowych żądań na sekundę na jednym komputerze, a tym samym stać się najszybszym ze wszystkich istniejących kontrolerów SDN .

Mio dostało się do wydania GHC 7.8.1. Osobiście uważam to za duży krok naprzód w wydajności Haskell. Bardzo interesujące byłoby porównanie wydajności istniejących aplikacji internetowych skompilowanych przez poprzednią wersję GHC i 7.8.1.

vlprans
źródło
6

Zdarzenia IMHO są dobre, ale programowanie za pomocą wywołań zwrotnych nie jest.

Większość problemów, które wyróżniają kodowanie i debugowanie aplikacji internetowych, pochodzi z tego, co czyni je skalowalnymi i elastycznymi. Najważniejszy, bezpaństwowy charakter HTTP. Zwiększa to nawigowalność, ale narzuca odwrócenie kontroli, gdy element IO (w tym przypadku serwer WWW) wywołuje różne procedury obsługi w kodzie aplikacji. Ten model zdarzeń - lub dokładniej mówiąc - model zwrotny - jest koszmarem, ponieważ wywołania zwrotne nie dzielą zmiennych zakresów, a intuicyjny widok nawigacji został utracony. Bardzo trudno jest zapobiec wszystkim możliwym zmianom stanu, gdy użytkownik porusza się w przód iw tył, między innymi problemami.

Można powiedzieć, że problemy są podobne do programowania GUI, w którym model zdarzeń działa dobrze, ale GUI nie mają nawigacji ani przycisku powrotu. To zwielokrotnia możliwe przejścia stanu w aplikacjach internetowych. Rezultatem próby rozwiązania tego problemu są ciężkie frameworki o skomplikowanych konfiguracjach, mnóstwo wszechobecnych magicznych identyfikatorów bez kwestionowania źródła problemu: model wywołania zwrotnego i nieodłączny brak współdzielenia zmiennych zakresów oraz brak sekwencjonowania, więc sekwencja musi być skonstruowane przez połączenie identyfikatorów.

Istnieją frameworki oparte na ramach, takich jak ocsigen (ocaml) nadmorski (smalltalk) WASH (przerwany, Haskell) i mflow (Haskell), które rozwiązują problem zarządzania stanem przy jednoczesnym zachowaniu możliwości nawigacji i pełnej REST. w tych ramach programista może wyrazić nawigację jako imperatywną sekwencję, w której program wysyła strony i czeka na odpowiedzi w jednym wątku, zmienne są w zasięgu, a przycisk Wstecz działa automatycznie. To z natury tworzy krótszy, bezpieczniejszy i bardziej czytelny kod, w którym nawigacja jest wyraźnie widoczna dla programisty. (uczciwe ostrzeżenie: Jestem programistą mflow)

agocorona
źródło
W node.js wywołania zwrotne są używane do obsługi asynchronicznych operacji we / wy, np. Do baz danych. Mówisz o czymś innym, co, choć interesujące, nie odpowiada na pytanie.
Robin Green
Masz rację. Trzy lata zajęło znalezienie odpowiedzi, która - mam nadzieję - spełniła twoje zastrzeżenia: github.com/transient-haskell
agocorona
Węzeł obsługuje teraz funkcje asynchroniczne, co oznacza, że ​​możesz pisać kod w stylu imperatywnym, który jest w rzeczywistości asynchroniczny. Wykorzystuje obietnice pod maską.
Eric Elliott,
5

Pytanie jest dość śmieszne, ponieważ 1) Haskell rozwiązał już ten problem w znacznie lepszy sposób i 2) w mniej więcej taki sam sposób, jak Erlang. Oto punkt odniesienia dla węzła: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks

Daj Haskellowi 4 rdzenie, a on może wykonać 100 000 (prostych) żądań na sekundę w jednej aplikacji. Węzeł nie może zrobić tak wiele i nie może skalować pojedynczej aplikacji między rdzeniami. I nie musisz nic robić, aby to czerpać, ponieważ środowisko uruchomieniowe Haskell nie blokuje. Jedynym innym (stosunkowo powszechnym) językiem, który ma nieblokujące się we / wy wbudowane w środowisko wykonawcze, jest Erlang.

Greg Weber
źródło
14
Śmieszny? Pytanie nie brzmi „czy Haskell ma odpowiedź”, ale raczej „jaka jest odpowiedź Haskell”. W chwili, gdy pytanie zostało zadane, GHC 7 nie został nawet wydany, więc Haskell nie był jeszcze „w grze” (może z wyjątkiem frameworków wykorzystujących libev jak Snap). Poza tym zgadzam się.
gawi
1
Nie wiem, czy to prawda, kiedy opublikowałeś tę odpowiedź, ale w rzeczywistości istnieją moduły węzłów, które pozwalają aplikacjom węzłów łatwo skalować się między rdzeniami. Link ten porównuje także plik node.js działający na jednym rdzeniu z haskell działającym na 4 rdzeniach. Chciałbym zobaczyć, jak działa ponownie w bardziej sprawiedliwej konfiguracji, ale niestety repozytorium github zniknęło.
Tim Gautier
2
Haskell korzystający z więcej niż 4 rdzeni obniża wydajność aplikacji. Był artykuł na ten temat, nad którym aktywnie pracowano, ale wciąż jest to problem. Tak więc uruchomienie 16 instancji Node.js na 16-rdzeniowym serwerze najprawdopodobniej będzie znacznie lepsze niż pojedyncza aplikacja ghc przy użyciu + RTS -N16, która rzeczywiście będzie wolniejsza niż + RTS -N1 z powodu tego błędu w czasie wykonywania. Jest tak, ponieważ używają tylko jednego IOManagera, który zwolni, gdy będzie używany z wieloma wątkami systemu operacyjnego. Mam nadzieję, że naprawią ten błąd, ale on istnieje od zawsze, więc nie miałbym wiele nadziei ...
Kr0e
Każdy, kto spojrzy na tę odpowiedź, powinien mieć świadomość, że Node może z łatwością przetworzyć 100 000 prostych żądań na jednym rdzeniu, a skalowanie bezstanowej aplikacji Node na wiele rdzeni jest niezwykle proste. pm2 -i max path/to/app.jsautomatycznie skaluje się do optymalnej liczby instancji w oparciu o dostępne rdzenie. Dodatkowo, Węzeł domyślnie również nie blokuje.
Eric Elliott,
1

Tak jak nodejs upuścił libev, tak samo Snap Haskell Web Framework upuścił libev .

Chawathe Vipul S.
źródło
1
Jak to odpowiada na pytanie?
dfeuer
1
@dfeuer Link musi brzmieć: Snap Haskell Web Framework upuścił libev, nie wiem, dlaczego formatowanie się nie udaje. Środowisko uruchomieniowe serwera węzłów zajmowało się przede wszystkim libevem Linuksa, podobnie jak Snap Web FrameWork. Haskell z Snap jest jak ECMAscript z nodejs, więc sposób, w jaki Snap ewoluuje wraz z nodejs, jest bardziej odpowiedni niż Haskell, który w tym kontekście może bardziej słusznie porównać z ECMAscript.
Chawathe Vipul S