Quelle est la réponse de Haskell à Node.js?

217

Je crois que la communauté Erlang n'est pas envieuse de Node.js car elle fait des E / S non bloquantes en natif et a des moyens de faire évoluer facilement les déploiements sur plus d'un processeur (quelque chose qui n'est même pas intégré dans Node.js). Plus de détails sur http://journal.dedasys.com/2010/04/29/erlang-vs-node-js et Node.js ou Erlang

Et Haskell? Haskell peut-il fournir certains des avantages de Node.js, à savoir une solution propre pour éviter de bloquer les E / S sans avoir recours à la programmation multi-thread?


Il y a beaucoup de choses qui sont attrayantes avec Node.js

  1. Événements: aucune manipulation de thread, le programmeur ne fournit que des rappels (comme dans le framework Snap)
  2. Les rappels sont garantis pour être exécutés dans un seul thread: aucune condition de concurrence possible.
  3. API simple et conviviale pour UNIX. Bonus: Excellent support HTTP. DNS également disponible.
  4. Chaque E / S est par défaut asynchrone. Cela permet d'éviter plus facilement les verrous. Cependant, trop de traitement CPU dans un rappel aura un impact sur les autres connexions (dans ce cas, la tâche doit être divisée en sous-tâches plus petites et reprogrammée).
  5. Même langage côté client et côté serveur. (Je ne vois cependant pas trop de valeur dans celui-ci. JQuery et Node.js partagent le modèle de programmation d'événements, mais le reste est très différent. Je ne vois tout simplement pas comment le partage de code entre côté serveur et côté client pourrait être utile dans la pratique.)
  6. Tout cela emballé dans un seul produit.
gawi
la source
17
Je pense que vous devriez plutôt poser cette question aux programmeurs .
Jonas
47
Ne pas inclure un morceau de code n'en fait pas une question subjective.
gawi
20
Je ne sais pas grand chose sur node.js, mais une chose m'a frappé dans votre question: pourquoi trouvez-vous la perspective de threads si désagréable? Les threads doivent être exactement la bonne solution pour le multiplexage des E / S. J'utilise le terme threads de manière large ici, y compris les processus d'Erlang. Peut-être que vous vous inquiétez des verrous et de l'état mutable? Vous n'avez pas à faire les choses de cette façon - utilisez la transmission de messages ou des transactions si cela est plus logique pour votre application.
Simon Marlow
9
@gawi Je ne pense pas que cela semble très facile à programmer - sans préemption, vous devez faire face à la possibilité de famine et de longues latences. Fondamentalement, les threads sont la bonne abstraction pour un serveur Web - il n'est pas nécessaire de traiter les E / S asynchrones et toutes les difficultés qui vont avec, faites-le simplement dans un thread. Soit dit en passant
Simon Marlow
3
"Les rappels sont garantis pour être exécutés dans un seul thread: aucune condition de concurrence possible." Faux. Vous pouvez facilement avoir des conditions de course dans Node.js; supposons simplement qu'une action d'E / S se termine avant une autre, et BOOM. Ce qui est en effet impossible est un type particulier de conditions de concurrence, à savoir l'accès simultané non synchronisé au même octet en mémoire.
droite le

Réponses:

219

Ok, donc après avoir regardé un peu la présentation de node.js vers laquelle @gawi m'a pointé, je peux en dire un peu plus sur la façon dont Haskell se compare à node.js. Dans la présentation, Ryan décrit certains des avantages de Green Threads, mais continue en disant qu'il ne trouve pas que le manque d'abstraction de thread soit un inconvénient. Je ne suis pas d'accord avec sa position, en particulier dans le contexte de Haskell: je pense que les abstractions que les threads fournissent sont essentielles pour rendre le code du serveur plus facile à obtenir et plus robuste. En particulier:

  • l'utilisation d'un thread par connexion vous permet d'écrire du code qui exprime la communication avec un seul client, plutôt que d'écrire du code qui traite avec tous les clients en même temps. Pensez-y comme ceci: un serveur qui gère plusieurs clients avec des threads ressemble presque à celui qui gère un seul client; la principale différence est qu'il y a forkquelque part dans le premier. Si le protocole que vous implémentez est complexe, la gestion simultanée de la machine d'état pour plusieurs clients devient assez délicate, tandis que les threads vous permettent de simplement écrire la communication avec un seul client. Le code est plus facile à obtenir correctement, et plus facile à comprendre et à maintenir.

  • les rappels sur un seul thread OS sont du multitâche coopératif, par opposition au multitâche préemptif, ce que vous obtenez avec les threads. Le principal inconvénient du multitâche coopératif est que le programmeur est responsable de s'assurer qu'il n'y a pas de famine. Il perd sa modularité: faites une erreur en un seul endroit, et il peut foutre tout le système. C'est vraiment quelque chose dont vous ne voulez pas vous soucier, et la préemption est la solution simple. De plus, la communication entre les rappels n'est pas possible (cela entraînerait un blocage).

  • la concurrence n'est pas difficile dans Haskell, car la plupart du code est pur et donc thread-safe par construction. Il existe de simples primitives de communication. Il est beaucoup plus difficile de se tirer une balle dans le pied avec la concurrence dans Haskell que dans une langue avec des effets secondaires illimités.

Simon Marlow
la source
42
Ok, donc j'obtiens que node.js est la solution à 2 problèmes: 1- la concurrence est difficile dans la plupart des langues, 2- l'utilisation des threads du système d'exploitation est expansive. La solution Node.js consiste à utiliser la concurrence basée sur les événements (w / libev) pour éviter la communication entre les threads et pour éviter les problèmes d'évolutivité des threads du système d'exploitation. Haskell n'a pas de problème n ° 1 à cause de la pureté. Pour # 2, Haskell a des threads légers + un gestionnaire d'événements récemment optimisé dans GHC pour des contextes à grande échelle. De plus, l'utilisation de Javascript ne peut être perçue comme un avantage pour aucun développeur Haskell. Pour certaines personnes utilisant le Snap Framework, Node.js est "tout simplement mauvais".
gawi
4
Le traitement des demandes est la plupart du temps une séquence d'opérations interdépendantes. J'ai tendance à convenir que l'utilisation des rappels pour chaque opération de blocage peut être fastidieuse. Les threads sont mieux adaptés que le rappel pour cela.
gawi
10
Oui! Et le tout nouveau multiplexage d'E / S dans GHC 7 améliore encore l'écriture des serveurs dans Haskell.
andreypopp
3
Votre premier point n'a pas beaucoup de sens pour moi (en tant qu'étranger) ... Lors du traitement d'une demande dans node.js, votre rappel concerne un seul client. La gestion de l'état ne devient une préoccupation que lors de la mise à l'échelle vers plusieurs processus, et même alors, il est assez facile d'utiliser les bibliothèques disponibles.
Ricardo Tomasi
12
Ce n'est pas une question distincte. Si cette question est une véritable recherche des meilleurs outils pour le travail à Haskell, ou une vérification de l'existence d'excellents outils pour le travail à Haskell, alors l'hypothèse implicite que la programmation multithread ne conviendrait pas doit être contestée, car Haskell ne fils plutôt différemment, comme le souligne Don Stewart. Les réponses qui expliquent pourquoi la communauté Haskell n'est pas non plus jalouse de Node.js sont très bien sur le sujet de cette question. La réponse de Gawi suggère que c'était une réponse appropriée à sa question.
AndrewC
154

Haskell peut-il fournir certains des avantages de Node.js, à savoir une solution propre pour éviter de bloquer les E / S sans avoir recours à la programmation multi-thread?

Oui, en fait, les événements et les threads sont unifiés dans Haskell.

  • Vous pouvez programmer des threads légers explicites (par exemple des millions de threads sur un seul ordinateur portable).
  • Ou; vous pouvez programmer dans un style événementiel asynchrone, basé sur une notification d'événement évolutive.

Les threads sont en fait implémentés en termes d'événements et s'exécutent sur plusieurs cœurs, avec une migration transparente des threads, avec des performances documentées et des applications.

Par exemple pour

Collections simultanées nbody sur 32 cœurs

texte alternatif

Dans Haskell, vous avez à la fois des événements et des threads, et comme ce sont tous les événements sous le capot.

Lisez le document décrivant la mise en œuvre.

Don Stewart
la source
2
Merci. J'ai besoin de digérer tout cela ... Cela semble être spécifique au GHC. Je suppose que c'est OK. Le langage Haskell est parfois tout ce que GHC peut compiler. De la même manière, la "plate-forme" Haskell est plus ou moins le runtime GHC.
gawi
1
@gawi: Cela et tous les autres packages qui y sont intégrés afin qu'il soit utile dès la sortie de la boîte. Et c'est la même image que j'ai vue dans mon cours CS; et la meilleure partie est qu'il n'est pas difficile à Haskell d'obtenir des résultats impressionnants similaires dans vos propres programmes.
Robert Massaioli
1
Salut Don, pensez-vous que vous pourriez créer un lien vers le serveur Web haskell qui fonctionne le mieux (Warp) lorsque vous répondez à des questions comme celles-ci? Voici la référence tout à fait pertinente contre Node.js: yesodweb.com/blog/2011/03/…
Greg Weber
4
Juste en théorie. Les «fils légers» Haskell ne sont pas aussi légers que vous le pensez. Il est beaucoup plus économique d'enregistrer un rappel sur une interface epoll que de planifier un soi-disant thread vert, ils sont bien sûr moins chers que les threads OS mais ils ne sont pas gratuits. La création de 100 000 d'entre eux utilise env. 350 Mo de mémoire et prenez du temps. Essayez 100 000 connexions avec node.js. Aucun problème du tout . Ce serait magique si ce n'était pas plus rapide car ghc utilise epoll sous le capot et ne peut donc pas être plus rapide que d'utiliser epoll directement. La programmation avec l'interface des threads est cependant assez agréable.
Kr0e
3
De plus: Le nouveau gestionnaire d'E / S (ghc) utilise un algorithme de planification qui a une complexité (m log n) (où m est le nombre de threads exécutables et n le nombre total de threads). Epoll a la complexité k (k est le nombre de fd lisibles / inscriptibles =. Donc ghc a O (k * m log n) sur toute la complexité qui n'est pas très bonne si vous faites face à des connexions à fort trafic. Node.js a juste la complexité linéaire causée par epoll. Et ne parlons pas des performances de Windows ... Node.js est beaucoup plus rapide car il utilise IOCP
Kr0e
20

Tout d'abord, je ne pense pas que node.js fasse la bonne chose en exposant tous ces rappels. Vous finissez par écrire votre programme en CPS (style de passage continu) et je pense que ce devrait être le travail du compilateur de faire cette transformation.

Événements: aucune manipulation de thread, le programmeur ne fournit que des rappels (comme dans le framework Snap)

Donc, avec cela à l'esprit, vous pouvez écrire en utilisant un style asynchrone si vous le souhaitez, mais en le faisant, vous manqueriez d'écrire dans un style synchrone efficace, avec un thread par demande. Haskell est ridiculement efficace en code synchrone, surtout par rapport à d'autres langages. Ce sont tous les événements en dessous.

Les rappels sont garantis pour être exécutés dans un seul thread: aucune condition de concurrence possible.

Vous pouvez toujours avoir une condition de concurrence dans node.js, mais c'est plus difficile.

Chaque demande est dans son propre fil. Lorsque vous écrivez du code qui doit communiquer avec d'autres threads, il est très simple de le rendre threadsafe grâce aux primitives de concurrence de haskell.

API simple et conviviale pour UNIX. Bonus: Excellent support HTTP. DNS également disponible.

Jetez un œil au piratage et voyez par vous-même.

Chaque E / S est par défaut asynchrone (cela peut parfois être ennuyeux). Cela permet d'éviter plus facilement les verrous. Cependant, trop de traitement CPU dans un rappel aura un impact sur les autres connexions (dans ce cas, la tâche doit être divisée en sous-tâches plus petites et reprogrammée).

Vous n'avez pas de tels problèmes, ghc distribuera votre travail entre les vrais threads du système d'exploitation.

Même langage côté client et côté serveur. (Je ne vois cependant pas trop de valeur dans celui-ci. JQuery et Node.js partagent le modèle de programmation d'événements, mais le reste est très différent. Je ne vois tout simplement pas comment le partage de code entre côté serveur et côté client pourrait être utile dans la pratique.)

Haskell ne peut probablement pas gagner ici ... non? Détrompez-vous, http://www.haskell.org/haskellwiki/Haskell_in_web_browser .

Tout cela emballé dans un seul produit.

Téléchargez ghc, lancez la cabale. Il y a un forfait pour chaque besoin.

dan_waterworth
la source
Je jouais juste l'avocat du diable. Donc, oui, je suis d'accord sur vos points. À l'exception de l'unification des langues côté client et côté serveur. Bien que je pense que c'est techniquement faisable, je ne pense pas que cela puisse éventuellement remplacer tout l'écosystème Javascript en place aujourd'hui (JQuery et ses amis). Bien que ce soit un argument avancé par les partisans de Node.js, je ne pense pas qu'il soit très important. Avez-vous vraiment besoin de partager autant de code entre votre couche de présentation et votre backend? Voulons-nous vraiment avoir des programmeurs connaissant une seule langue?
gawi
Le véritable avantage est que vous pouvez rendre les pages côté serveur et côté client, ce qui facilite la création de pages en temps réel.
dan_waterworth
@dan_waterworth exactement, voir météore ou derby.js
mb21
1
@gawi Nous avons des services de production où 85% du code est partagé entre le client et le serveur. Ceci est connu sous le nom de JavaScript universel dans la communauté. Nous utilisons React pour restituer dynamiquement du contenu sur le serveur afin de réduire le temps de premier rendu utile dans le client. Bien que je sache que vous pouvez exécuter Haskell dans le navigateur, je ne connais aucun ensemble de meilleures pratiques "universelles Haskell" qui permettent le rendu côté serveur et côté client en utilisant la même base de code.
Eric Elliott
8

Personnellement, je considère Node.js et la programmation avec des rappels comme un niveau inutilement bas et un peu contre nature. Pourquoi programmer avec des rappels alors qu'un bon runtime tel que celui trouvé dans GHC peut gérer les rappels pour vous et le faire assez efficacement?

Dans l'intervalle, le temps d'exécution du GHC s'est considérablement amélioré: il comporte désormais un "nouveau nouveau gestionnaire d'E / S" appelé MIO, où "M" signifie multicœur, je crois. Il s'appuie sur les fondations du gestionnaire d'E / S existant et son objectif principal est de surmonter la cause de la dégradation des performances de 4+ cœurs. Les performances fournies dans cet article sont assez impressionnantes. Voyez-vous:

Avec Mio, des serveurs HTTP réalistes à l'échelle Haskell jusqu'à 20 cœurs de processeur, atteignant des performances de pointe jusqu'à un facteur de 6,5x par rapport aux mêmes serveurs utilisant les versions précédentes de GHC. La latence des serveurs Haskell est également améliorée: [...] sous une charge modérée, réduit le temps de réponse attendu de 5,7x par rapport aux versions précédentes de GHC

Et:

Nous montrons également qu'avec Mio, McNettle (un contrôleur SDN écrit en Haskell) peut évoluer efficacement vers plus de 40 cœurs, atteindre un débit de plus de 20 millions de nouvelles demandes par seconde sur une seule machine, et devenir ainsi le plus rapide de tous les contrôleurs SDN existants .

Mio a intégré la version GHC 7.8.1. Personnellement, je vois cela comme une avancée majeure dans la performance de Haskell. Il serait très intéressant de comparer les performances des applications Web existantes compilées par la version précédente de GHC et 7.8.1.

vlprans
la source
6

Les événements à mon humble avis sont bons, mais la programmation au moyen de rappels ne l'est pas.

La plupart des problèmes qui rendent le codage et le débogage spéciaux des applications Web proviennent de ce qui les rend évolutifs et flexibles. Le plus important, la nature sans état de HTTP. Cela améliore la navigabilité, mais cela impose une inversion de contrôle où l'élément IO (le serveur Web dans ce cas) appelle différents gestionnaires dans le code d'application. Ce modèle d'événement - ou modèle de rappel, plus précisément - est un cauchemar, car les rappels ne partagent pas des portées variables et une vue intuitive de la navigation est perdue. Il est très difficile d'empêcher tous les changements d'état possibles lorsque l'utilisateur navigue d'avant en arrière, entre autres problèmes.

On peut dire que les problèmes sont similaires à la programmation GUI où le modèle d'événement fonctionne bien, mais les GUI n'ont pas de navigation et pas de bouton de retour. Cela multiplie les transitions d'états possibles dans les applications Web. Le résultat de la tentative de résoudre ces problèmes sont des cadres lourds avec des configurations compliquées, de nombreux identifiants magiques omniprésents sans remettre en cause la racine du problème: le modèle de rappel et son manque inhérent de partage de portées variables, et pas de séquencement, de sorte que la séquence doit être construit en reliant les identifiants.

Il existe des cadres basés séquentiels comme ocsigen (ocaml) bord de mer (smalltalk) WASH (abandonné, Haskell) et mflow (Haskell) qui résolvent le problème de la gestion des états tout en maintenant la navigabilité et la plénitude REST. dans ces cadres, le programmeur peut exprimer la navigation comme une séquence impérative où le programme envoie des pages et attend les réponses dans un seul thread, les variables sont dans la portée et le bouton de retour fonctionne automatiquement. Cela produit par nature un code plus court, plus sûr et plus lisible où la navigation est clairement visible pour le programmeur. (juste avertissement: je suis le développeur de mflow)

agocorona
la source
Dans node.js, les rappels sont utilisés pour gérer les E / S asynchrones, par exemple vers les bases de données. Vous parlez de quelque chose de différent qui, bien qu'intéressant, ne répond pas à la question.
Robin Green
Vous avez raison. Il a fallu trois ans pour avoir une réponse qui, je l'espère, répondra à vos objections: github.com/transient-haskell
agocorona
Le nœud prend désormais en charge les fonctions asynchrones, ce qui signifie que vous pouvez écrire du code de style impératif qui est en fait asynchrone. Il utilise des promesses sous le capot.
Eric Elliott
5

La question est assez ridicule car 1) Haskell a déjà résolu ce problème d'une bien meilleure manière et 2) à peu près de la même manière qu'Erlang. Voici la référence par rapport au nœud: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks

Donnez à Haskell 4 cœurs et il peut faire 100 000 requêtes (simples) par seconde dans une seule application. Le nœud ne peut pas en faire autant et ne peut pas faire évoluer une seule application sur plusieurs cœurs. Et vous n'avez rien à faire pour récolter cela car le runtime Haskell n'est pas bloquant. Le seul autre langage (relativement commun) qui intègre des E / S non bloquantes dans l'exécution est Erlang.

Greg Weber
la source
14
Ridicule? La question n'est pas "Haskell a-t-il une réponse" mais plutôt "quelle est la réponse Haskell". Au moment où la question a été posée, GHC 7 n'était même pas sorti, donc Haskell n'était pas encore "dans le jeu" (sauf peut-être pour les frameworks utilisant libev comme Snap). A part ça, je suis d'accord.
gawi
1
Je ne sais pas si cela était vrai lorsque vous avez publié cette réponse, mais maintenant, il existe en fait des modules de nœuds qui permettent aux applications de nœuds de s'adapter facilement aux différents cœurs. En outre, ce lien compare node.js fonctionnant sur un seul cœur à haskell fonctionnant sur 4 cœurs. J'aimerais le revoir fonctionner dans une configuration plus juste, mais hélas, le dépôt github a disparu.
Tim Gautier
2
Haskell utilisant plus de 4 cœurs dégrade les performances de l'application. Il y avait un document sur cette question, il est activement travaillé, mais c'est toujours un problème. Ainsi, l'exécution de 16 instances de Node.js sur un serveur 16 cœurs sera probablement bien meilleure qu'une seule application ghc utilisant + RTS -N16 qui sera en effet plus lente que + RTS -N1 en raison de ce bogue d'exécution. C'est parce qu'ils utilisent un seul IOManager qui ralentira lorsqu'il est utilisé avec de nombreux threads de système d'exploitation. J'espère qu'ils vont corriger ce bug mais il existe depuis toujours donc je n'aurais pas beaucoup d'espoir ...
Kr0e
Quiconque regarde cette réponse doit savoir que Node peut facilement traiter 100 000 requêtes simples sur un seul cœur et il est très facile de faire évoluer une application Node sans état sur plusieurs cœurs. pm2 -i max path/to/app.jsévoluera automatiquement vers le nombre optimal d'instances en fonction des cœurs disponibles. De plus, Node est également non bloquant par défaut.
Eric Elliott
1

Tout comme nodejs a chuté libev le Web Framework snap Haskell a chuté libev aussi.

Chawathe Vipul S
la source
1
Comment cela répond-il à la question?
dfeuer
1
@dfeuer Le lien doit se lire comme, Snap Haskell Web Framework a abandonné libev, je ne sais pas pourquoi le formatage échoue. Le temps d'exécution du serveur de nœuds concernait entièrement Linux libev au début, tout comme Snap Web FrameWork. Haskell avec Snap est comme ECMAscript avec nodejs, donc comment Snap évolue avec nodejs est plus pertinent que Haskell, ce qui peut être comparé à juste titre avec ECMAscript dans ce contexte.
Chawathe Vipul S