Qual è la risposta Haskell a Node.js?


217

Credo che la comunità Erlang non sia invidiosa di Node.js in quanto esegue l'I / O non bloccante in modo nativo e ha modi per ridimensionare facilmente le distribuzioni su più di un processore (qualcosa che non è nemmeno incorporato in Node.js). Maggiori dettagli su http://journal.dedasys.com/2010/04/29/erlang-vs-node-js e Node.js o Erlang

Che mi dici di Haskell? Haskell può offrire alcuni dei vantaggi di Node.js, ovvero una soluzione pulita per evitare il blocco dell'I / O senza ricorrere alla programmazione multi-thread?


Ci sono molte cose interessanti con Node.js

  1. Eventi: nessuna manipolazione del thread, il programmatore fornisce solo callback (come nel framework Snap)
  2. I callback sono garantiti per essere eseguiti in un singolo thread: nessuna condizione di competizione possibile.
  3. API amichevole e semplice per UNIX. Bonus: eccellente supporto HTTP. DNS disponibile anche.
  4. Ogni I / O è di default asincrono. Ciò rende più semplice evitare i blocchi. Tuttavia, un'eccessiva elaborazione della CPU in un callback influirà su altre connessioni (in questo caso, l'attività dovrebbe essere suddivisa in attività secondarie più piccole e riprogrammata).
  5. Stessa lingua per lato client e lato server. (Non vedo troppo valore in questo, tuttavia. JQuery e Node.js condividono il modello di programmazione degli eventi, ma il resto è molto diverso. Non riesco proprio a vedere come la condivisione del codice tra lato server e lato client potrebbe essere utile in pratica.)
  6. Tutto questo confezionato in un unico prodotto.

17
Penso che dovresti invece fare questa domanda sui programmatori .
Jonas,

47
Non includere un pezzo di codice non lo rende una domanda soggettiva.
gawi,

20
Non so molto su node.js, ma una cosa mi ha colpito della tua domanda: perché trovi così spiacevole la prospettiva dei thread? I thread dovrebbero essere esattamente la soluzione giusta per l'I / O multiplexing. Uso qui il termine thread in senso lato, compresi i processi di Erlang. Forse sei preoccupato per le serrature e lo stato mutevole? Non devi fare le cose in questo modo: usa il passaggio di messaggi o le transazioni se questo ha più senso per la tua applicazione.
Simon Marlow,

9
@gawi Non penso che sia molto facile da programmare - senza alcuna anticipazione, devi affrontare la possibilità di morire di fame e lunghe latenze. Fondamentalmente i thread sono l'astrazione giusta per un server Web: non è necessario gestire l'I / O asincrono e tutte le difficoltà che ne derivano, basta farlo in un thread. Per inciso, ho scritto un documento sui server Web di Haskell che potresti trovare interessante: haskell.org/~simonmar/papers/web-server-jfp.pdf
Simon Marlow

3
"I callback sono garantiti per essere eseguiti in un singolo thread: nessuna condizione di competizione possibile." Sbagliato. Puoi facilmente avere condizioni di gara in Node.js; supponiamo che un'azione I / O venga completata prima di un'altra e BOOM. Ciò che è davvero impossibile è un particolare tipo di condizioni di gara, vale a dire l'accesso simultaneo non sincronizzato allo stesso byte in memoria.
destra del

Risposte:


219

Ok, quindi dopo aver visto un po 'della presentazione node.js che @gawi mi ha indicato, posso dire qualcosa in più su come Haskell paragona a node.js. Nella presentazione, Ryan descrive alcuni dei vantaggi di Green Threads, ma continua dicendo che non trova la mancanza di un'astrazione di thread uno svantaggio. Non sarei d'accordo con la sua posizione, in particolare nel contesto di Haskell: penso che le astrazioni fornite dai thread siano essenziali per rendere il codice del server più facile da ottenere e più robusto. In particolare:

  • l'utilizzo di un thread per connessione consente di scrivere codice che esprime la comunicazione con un singolo client, anziché quel codice di scrittura che si occupa tutti i client contemporaneamente. Pensala in questo modo: un server che gestisce più client con thread sembra quasi uguale a quello che gestisce un singolo client; la differenza principale è che c'è un forkposto nel primo. Se il protocollo che stai implementando è del tutto complesso, la gestione della macchina a stati per più client contemporaneamente diventa piuttosto complicata, mentre i thread ti consentono di creare script per la comunicazione con un singolo client. Il codice è più facile da ottenere e più facile da capire e mantenere.

  • i callback su un singolo thread del sistema operativo sono il multitasking cooperativo, al contrario del multitasking preventivo, che è ciò che si ottiene con i thread. Il principale svantaggio del multitasking cooperativo è che il programmatore è responsabile di assicurarsi che non ci sia fame. Perde la modularità: commette un errore in un unico punto e può rovinare l'intero sistema. Questo è davvero qualcosa di cui non devi preoccuparti, e la prevenzione è la soluzione semplice. Inoltre, la comunicazione tra callback non è possibile (sarebbe deadlock).

  • la concorrenza non è difficile in Haskell, perché la maggior parte del codice è pura e quindi è thread-safe per costruzione. Esistono semplici primitive di comunicazione. È molto più difficile spararsi ai piedi con la concorrenza in Haskell che in una lingua con effetti collaterali senza restrizioni.


42
Ok, quindi ho capito che node.js è la soluzione a 2 problemi: 1- la concorrenza è difficile nella maggior parte delle lingue, 2- l'utilizzo dei thread del sistema operativo è espansivo. La soluzione Node.js consiste nell'utilizzare la concorrenza basata sugli eventi (w / libev) per evitare la comunicazione tra thread ed evitare problemi di scalabilità dei thread del sistema operativo. Haskell non ha problemi n. 1 a causa della purezza. Per # 2, Haskell ha thread leggeri + gestore eventi che è stato recentemente ottimizzato in GHC per contesti su larga scala. Inoltre, l'uso di Javascript non può essere percepito come un vantaggio per nessuno sviluppatore Haskell. Per alcune persone che usano Snap Framework, Node.js è "semplicemente cattivo".
gawi,

4
L'elaborazione della richiesta è per lo più una sequenza di operazioni interdipendenti. Tendo a concordare sul fatto che l'uso di callback per ogni operazione di blocco può essere complicato. I thread sono più adatti del callback per questo.
gawi,

10
Sì! E il nuovissimo multiplexing I / O in GHC 7 rende ancora migliori i server di scrittura in Haskell.
Andreypopp,

3
Il tuo primo punto non ha molto senso per me (come estraneo) ... Quando elabori una richiesta in node.js il tuo callback si occupa di un singolo client. La gestione dello stato diventa qualcosa di cui preoccuparsi solo quando si esegue il ridimensionamento a più processi e anche in questo caso è abbastanza semplice utilizzare le librerie disponibili.
Ricardo Tomasi,

12
Non è un problema separato. Se questa domanda è una vera ricerca degli strumenti migliori per il lavoro in Haskell o un controllo sulla presenza di strumenti eccellenti per il lavoro in Haskell, allora l'assunto implicito che la programmazione multi-thread sarebbe inadatta deve essere sfidato, perché Haskell lo fa le discussioni in modo piuttosto diverso, come sottolinea Don Stewart. Le risposte che spiegano perché anche la community di Haskell non è gelosa di Node.js sono molto in tema per questa domanda. La risposta di Gawi suggerisce che era una risposta appropriata alla sua domanda.
AndrewC,

154

Haskell può offrire alcuni dei vantaggi di Node.js, ovvero una soluzione pulita per evitare il blocco dell'I / O senza ricorrere alla programmazione multi-thread?

Sì, in effetti eventi e thread sono unificati in Haskell.

  • È possibile programmare in thread espliciti e leggeri (ad esempio milioni di thread su un singolo laptop).
  • O; puoi programmare in uno stile asincrono basato sugli eventi, basato su una notifica di eventi scalabile.

Le discussioni sono in realtà implementati in termini di eventi e vengono eseguiti su più core, con una migrazione senza interruzioni, con prestazioni documentate e applicazioni.

Ad esempio per

Collezioni simultanee nessuno su 32 core

testo alternativo

In Haskell hai sia eventi che discussioni, e come lo sono tutti gli eventi sotto il cofano.

Leggi l'articolo che descrive l'implementazione.


2
Grazie. Devo digerire tutto questo ... Questo sembra essere specifico del GHC. Immagino sia OK. Il linguaggio Haskell è talvolta come qualsiasi cosa GHC possa compilare. Allo stesso modo, la "piattaforma" di Haskell è più o meno il tempo di esecuzione del GHC.
gawi,

1
@gawi: Questo e tutti gli altri pacchetti che vengono raggruppati in modo da renderli utili fin dall'inizio. E questa è la stessa immagine che ho visto nel mio corso CS; e la parte migliore è che in Haskell non è difficile ottenere risultati simili nei propri programmi.
Robert Massaioli

1
Ciao Don, pensi di poter collegarti al web server haskell che funziona meglio (Warp) quando rispondi a domande come queste? Ecco il benchmark abbastanza rilevante contro Node.js: yesodweb.com/blog/2011/03/…
Greg Weber,

4
In teoria. I "fili leggeri" di Haskell non sono così leggeri come pensi. È molto più economico registrare un callback su un'interfaccia epoll rispetto alla pianificazione di un cosiddetto thread verde, ovviamente sono più economici dei thread del sistema operativo ma non sono gratuiti. Crearne 100.000 utilizza circa. 350 MB di memoria e richiedere del tempo. Prova 100.000 connessioni con node.js. Nessun problema . Sarebbe magico se non fosse più veloce poiché ghc usa epoll sotto il cofano, quindi non possono essere più veloci dell'uso diretto di epoll. La programmazione con l'interfaccia dei thread è piuttosto carina, comunque.
Kr0e

3
Inoltre: il nuovo IO manager (ghc) utilizza un algoritmo di schedulazione che ha (m log n) complessità (dove m è il numero di thread eseguibili e n il numero totale di thread). Epoll ha complessità k (k è il numero di fd leggibili / scrivibili =. Quindi ghc ha O (k * m log n) su tutta la complessità che non è molto buona se si affrontano connessioni ad alto traffico. Node.js ha causato solo la complessità lineare di epoll. E non parliamo delle prestazioni di Windows ... Node.js è molto più veloce perché usa IOCP.
Kr0e

20

Innanzitutto, non credo che node.js stia facendo la cosa giusta esponendo tutti quei callback. Finisci per scrivere il tuo programma in CPS (stile di passaggio di continuazione) e penso che dovrebbe essere il lavoro del compilatore fare quella trasformazione.

Eventi: nessuna manipolazione del thread, il programmatore fornisce solo callback (come nel framework Snap)

Quindi, con questo in mente, puoi scrivere usando uno stile asincrono, se lo desideri, ma così facendo perderesti di scrivere in uno stile sincrono efficiente, con un thread per richiesta. Haskell è incredibilmente efficiente nel codice sincrono, soprattutto se confrontato con altre lingue. Sono tutti gli eventi sottostanti.

I callback sono garantiti per essere eseguiti in un singolo thread: nessuna condizione di competizione possibile.

Potresti avere ancora una race condition in node.js, ma è più difficile.

Ogni richiesta è nel proprio thread. Quando scrivi codice che deve comunicare con altri thread, è molto semplice renderlo sicuro grazie alle primitive di concorrenza di haskell.

API amichevole e semplice per UNIX. Bonus: eccellente supporto HTTP. DNS disponibile anche.

Dai un'occhiata all'hackage e vedi di persona.

Per impostazione predefinita, ogni I / O è asincrono (a volte può essere fastidioso). Ciò rende più semplice evitare i blocchi. Tuttavia, un'eccessiva elaborazione della CPU in un callback influirà su altre connessioni (in questo caso, l'attività dovrebbe essere suddivisa in attività secondarie più piccole e riprogrammata).

Non hai questi problemi, ghc distribuirà il tuo lavoro tra thread del sistema operativo reale.

Stessa lingua per lato client e lato server. (Non vedo troppo valore in questo, tuttavia. JQuery e Node.js condividono il modello di programmazione degli eventi, ma il resto è molto diverso. Non riesco proprio a vedere come la condivisione del codice tra lato server e lato client potrebbe essere utile in pratica.)

Haskell non può assolutamente vincere qui ... giusto? Ripensaci, http://www.haskell.org/haskellwiki/Haskell_in_web_browser .

Tutto questo confezionato in un unico prodotto.

Scarica ghc, accendi la cabala. C'è un pacchetto per ogni esigenza.


Stavo solo giocando l'avvocato del diavolo. Quindi sì, sono d'accordo sui tuoi punti. Tranne l'unificazione della lingua lato client e lato server. Mentre penso che sia tecnicamente fattibile, non penso che alla fine potrebbe sostituire tutto l'ecosistema Javascript esistente oggi (JQuery e amici). Sebbene sia un argomento avanzato dai sostenitori di Node.js, non penso che sia molto importante. Hai davvero bisogno di condividere quel tanto codice tra il tuo livello di presentazione e il tuo backend? Intendiamo davvero avere programmatori che conoscono solo una lingua?
gawi,

La vera vittoria è che puoi rendere le pagine sia sul lato server che sul lato client rendendo più semplice la creazione di pagine in tempo reale.
dan_waterworth,

@dan_waterworth esattamente, vedi meteor o derby.js
mb21

1
@gawi Abbiamo servizi di produzione in cui l'85% del codice è condiviso tra client e server. Questo è noto come JavaScript universale nella community. Stiamo utilizzando React per eseguire il rendering dinamico del contenuto sul server per ridurre il tempo necessario per il primo rendering utile nel client. Sebbene sia a conoscenza del fatto che è possibile eseguire Haskell nel browser, non sono a conoscenza di alcuna serie di best practice "universali di Haskell" che consentono il rendering sul lato server e sul lato client utilizzando la stessa base di codice.
Eric Elliott,

8

Personalmente vedo Node.js e la programmazione con callback come inutilmente di basso livello e un po 'innaturale. Perché programmare con i callback quando un buon runtime come quello trovato in GHC può gestire i callback per te e farlo in modo abbastanza efficiente?

Nel frattempo, il runtime di GHC è notevolmente migliorato: ora presenta un "nuovo nuovo IO manager" chiamato MIO in cui "M" sta per multicore credo. Si basa sulle fondamenta dell'IO Manager esistente e il suo obiettivo principale è superare la causa del degrado delle prestazioni di oltre 4 core. I numeri delle prestazioni forniti in questo documento sono piuttosto impressionanti. Vedi te stesso:

Con Mio, i server HTTP realistici in Haskell scalano a 20 core della CPU, raggiungendo prestazioni di picco fino a un fattore di 6,5x rispetto agli stessi server che utilizzano le versioni precedenti di GHC. Anche la latenza dei server Haskell è migliorata: [...] con un carico moderato, riduce i tempi di risposta previsti di 5,7x rispetto alle versioni precedenti di GHC

E:

Mostriamo anche che con Mio, McNettle (un controller SDN scritto in Haskell) può scalare in modo efficace a oltre 40 core, raggiungere una trasmissione di oltre 20 milioni di nuove richieste al secondo su una singola macchina e quindi diventare il più veloce di tutti i controller SDN esistenti .

Mio è diventato GHC 7.8.1. Personalmente vedo questo come un importante passo avanti nella performance di Haskell. Sarebbe molto interessante confrontare le prestazioni delle applicazioni Web esistenti compilate dalla versione precedente di GHC e 7.8.1.


6

Gli eventi IMHO sono buoni, ma la programmazione tramite callback non lo è.

La maggior parte dei problemi che rendono speciali la codifica e il debug delle applicazioni Web deriva da ciò che le rende scalabili e flessibili. La più importante, la natura apolide di HTTP. Ciò migliora la navigabilità, ma impone un'inversione del controllo in cui l'elemento IO (il server Web in questo caso) chiama gestori diversi nel codice dell'applicazione. Questo modello di eventi - o il modello di callback, detto più accuratamente - è un incubo, poiché i callback non condividono ambiti variabili e si perde una vista intuitiva della navigazione. È molto difficile prevenire tutti i possibili cambiamenti di stato quando l'utente naviga avanti e indietro, tra gli altri problemi.

Si può dire che i problemi sono simili alla programmazione della GUI in cui il modello di eventi funziona correttamente, ma le GUI non hanno navigazione e nessun pulsante Indietro. Ciò moltiplica le transizioni di stato possibili nelle applicazioni Web. Il risultato del tentativo di risolvere questi problemi sono quadri pesanti con configurazioni complicate, un sacco di pervasivi identificatori magici senza mettere in discussione la radice del problema: il modello di callback e la sua intrinseca mancanza di condivisione di ambiti variabili e nessun sequenziamento, quindi la sequenza deve essere costruito collegando identificatori.

Esistono framework basati sequenziali come ocsigen (ocaml) seaside (smalltalk) WASH (fuori produzione, Haskell) e mflow (Haskell) che risolvono il problema della gestione dello stato mantenendo la navigabilità e la completezza REST. all'interno di questi framework, il programmatore può esprimere la navigazione come una sequenza imperativa in cui il programma invia pagine e attende risposte in un singolo thread, le variabili rientrano nell'ambito e il pulsante Indietro funziona automaticamente. Ciò produce intrinsecamente un codice più breve, più sicuro e più leggibile in cui la navigazione è chiaramente visibile al programmatore. (discreto avvertimento: sono lo sviluppatore di mflow)


In node.js i callback vengono utilizzati per la gestione degli I / O asincroni, ad esempio per i database. Stai parlando di qualcosa di diverso che, sebbene interessante, non risponde alla domanda.
Robin Green,

Hai ragione. Ci sono voluti tre anni per avere una risposta che, spero, soddisfi le tue obiezioni: github.com/transient-haskell
agocorona

Il nodo ora supporta le funzioni asincrone, il che significa che è possibile scrivere codice in stile imperativo che è effettivamente asincrono. Usa le promesse sotto il cofano.
Eric Elliott,

5

La domanda è piuttosto ridicola perché 1) Haskell ha già risolto questo problema in modo molto migliore e 2) più o meno allo stesso modo di Erlang. Ecco il benchmark rispetto al nodo: http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks

Dai a Haskell 4 core e può fare 100k (semplici) richieste al secondo in una singola applicazione. Il nodo non può fare altrettanto, e non può ridimensionare una singola applicazione tra i core. E non devi fare nulla per raccoglierlo perché il runtime di Haskell non è bloccante. L'unico altro linguaggio (relativamente comune) che ha IO non bloccante integrato nel runtime è Erlang.


14
Ridicolo? La domanda non è "Haskell ha una risposta" ma piuttosto "qual è la risposta di Haskell". Al momento della domanda, GHC 7 non era nemmeno stato rilasciato, quindi Haskell non era ancora "nel gioco" (tranne forse per i framework che usano libev come Snap). A parte questo, sono d'accordo.
gawi,

1
Non so se questo fosse vero quando hai pubblicato questa risposta, ma ora ci sono, in effetti, moduli di nodo che consentono alle app di nodo di scalare facilmente tra i core. Inoltre, quel collegamento sta confrontando node.js in esecuzione su un singolo core con haskell in esecuzione su 4 core. Mi piacerebbe vederlo funzionare di nuovo in una configurazione più corretta, ma purtroppo, il repository github è sparito.
Tim Gautier,

2
Haskell utilizzando più di 4 core peggiora le prestazioni dell'applicazione. C'era un documento su questo problema, ha funzionato attivamente ma è ancora un problema. Quindi eseguire 16 istanze di Node.js su un server 16 core sarà molto probabilmente molto meglio di una singola applicazione ghc usando + RTS -N16 che in effetti sarà più lenta di + RTS -N1 a causa di questo bug di runtime. È perché usano solo un IOManager che rallenterà se utilizzato con molti thread del sistema operativo. Spero che risolveranno questo errore ma esiste da sempre quindi non avrei molte speranze ...
Kr0e

Chiunque cerchi questa risposta dovrebbe essere consapevole che Node può elaborare facilmente 100.000 richieste semplici su un singolo core ed è banalmente facile ridimensionare un'applicazione Node senza stato su molti core. pm2 -i max path/to/app.jssi ridimensionerà automaticamente al numero ottimale di istanze in base ai core disponibili. Inoltre, Node è anche non bloccante per impostazione predefinita.
Eric Elliott,

1

1
Come risponde alla domanda?
Dfeuer,

1
@dfeuer Il link deve leggere come, Snap Haskell Web Framework ha lasciato cadere libev, non so perché la formattazione non riesca. Il runtime del server dei nodi era interamente basato su libev Linux quando è iniziato, così come Snap Web FrameWork. Haskell con Snap è come ECMAscript con nodejs, quindi il modo in cui Snap si evolve insieme a nodejs è più rilevante di Haskell, che può più giustamente confrontato con ECMAscript in questo contesto.
Chawathe Vipul S,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.