Tecnicamente, perché i processi in Erlang sono più efficienti dei thread del sistema operativo?


170

Caratteristiche di Erlang

Da Erlang Programming (2009):

La concorrenza di Erlang è veloce e scalabile. I suoi processi sono leggeri in quanto la macchina virtuale Erlang non crea un thread del sistema operativo per ogni processo creato. Vengono creati, pianificati e gestiti nella VM, indipendentemente dal sistema operativo sottostante. Di conseguenza, il tempo di creazione del processo è dell'ordine dei microsecondi e indipendente dal numero di processi esistenti contemporaneamente. Confronta questo con Java e C #, dove per ogni processo viene creato un thread del sistema operativo sottostante: otterrai alcuni confronti molto competitivi, con Erlang che supera notevolmente entrambe le lingue.

Dalla programmazione orientata alla concorrenza in Erlang (pdf) (diapositive) (2003):

Osserviamo che il tempo impiegato per creare un processo Erlang è costante 1µs fino a 2.500 processi; successivamente aumenta a circa 3µs per un massimo di 30.000 processi. Le prestazioni di Java e C # sono mostrate nella parte superiore della figura. Per un numero limitato di processi sono necessari circa 300 µs per creare un processo. La creazione di oltre duemila processi è impossibile.

Vediamo che per un massimo di 30.000 processi il tempo per inviare un messaggio tra due processi Erlang è di circa 0,8 µs. Per C # sono necessari circa 50 µs per messaggio, fino al numero massimo di processi (che era di circa 1800 processi). Java era anche peggio, per un massimo di 100 processi ci sono voluti circa 50 µs per messaggio, successivamente è aumentato rapidamente a 10 ms per messaggio quando c'erano circa 1000 processi Java.

I miei pensieri

Tecnicamente non capisco perfettamente perché i processi Erlang siano molto più efficienti nel generare nuovi processi e abbiano impronte di memoria molto più piccole per processo. Sia il sistema operativo che la VM Erlang devono eseguire la pianificazione, i cambi di contesto e tenere traccia dei valori nei registri e così via ...

Semplicemente perché i thread del sistema operativo non sono implementati allo stesso modo dei processi in Erlang? Devono supportare qualcosa in più? E perché hanno bisogno di un footprint di memoria maggiore? E perché hanno una generazione e una comunicazione più lente?

Tecnicamente, perché i processi in Erlang sono più efficienti dei thread del sistema operativo quando si tratta di spawn e comunicazione? E perché i thread nel sistema operativo non possono essere implementati e gestiti nello stesso modo efficiente? E perché i thread del sistema operativo hanno un footprint di memoria maggiore, oltre a una generazione e una comunicazione più lente?

Più lettura


1
Prima di tentare di capire il motivo per cui un'ipotesi è vera, è necessario stabilire se l'ipotesi è vera - ad esempio, supportata dall'evidenza. Hai riferimenti per confronti simili per dimostrare che un processo Erlang è effettivamente più efficiente di (diciamo) un thread Java su una JVM aggiornata? Oppure un'app C che utilizza direttamente il processo OS e il supporto thread? (Il secondo mi sembra molto, molto improbabile. Il primo solo un po 'probabile.) Voglio dire, con un ambiente abbastanza limitato (il punto di Francisco), potrebbe essere vero, ma vorrei vedere i numeri.
TJ Crowder,

1
@Donal: come nel caso di tante altre affermazioni assolute. :-)
TJ Crowder,

1
@Jonas: Grazie, ma sono arrivato alla data (1998-11-02) e alla versione JVM (1.1.6) e mi sono fermato. La JVM di Sun è migliorata abbastanza negli ultimi 11,5 anni (e presumibilmente anche l'interprete di Erlang), in particolare nell'area del threading. (Giusto per essere chiari, non sto dicendo che l'ipotesi non sia vera [e Francisco e Donal hanno sottolineato perché Erland potrebbe essere in grado di fare qualcosa lì]; sto dicendo che non dovrebbe essere preso al valore nominale senza essere controllato.)
TJ Crowder

1
@Jonas: "... ma immagino che tu possa farlo ad Erlang ..." È quella parte "indovina", amico. :-) Stai indovinando che il processo di commutazione di Erlang passa oltre le migliaia. Stai indovinando che lo fa meglio dei thread Java o OS. L'ipotesi e lo sviluppo del software non sono un'ottima combinazione. :-) Ma penso di aver fatto il punto.
TJ Crowder,

17
@TJ Crowder: installa erlang ed esegui erl +P 1000100 +hms 100e quindi digita {_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).e quindi attendi circa tre minuti per il risultato. È così semplice Richiede 140us per processo e 1 GB di RAM intera sul mio laptop. Ma è direttamente dalla shell, dovrebbe essere migliore dal codice compilato.
Hynek -Pichi- Vychodil,

Risposte:


113

Esistono diversi fattori che contribuiscono:

  1. I processi Erlang non sono processi OS. Sono implementati dalla VM Erlang utilizzando un modello di threading cooperativo leggero (preventivo a livello di Erlang, ma sotto il controllo di un runtime pianificato in modo cooperativo). Ciò significa che è molto più economico cambiare contesto, perché cambiano solo in punti noti e controllati e quindi non devono salvare l'intero stato della CPU (normale, registri SSE e FPU, mappatura dello spazio degli indirizzi, ecc.).
  2. I processi di Erlang utilizzano stack allocati dinamicamente, che iniziano molto piccoli e crescono se necessario. Ciò consente la generazione di molte migliaia - persino milioni - di processi Erlang senza risucchiare tutta la RAM disponibile.
  3. Erlang era a thread singolo, il che significa che non era necessario garantire la sicurezza del thread tra i processi. Ora supporta SMP, ma l'interazione tra i processi Erlang sullo stesso scheduler / core è ancora molto leggera (ci sono code di esecuzione separate per core).

6
Al secondo punto: e se il processo non è ancora stato eseguito, non vi è alcun motivo per cui allo stack sia assegnato. Inoltre: diversi trucchi possono essere giocati giocherellando con il GC di un processo in modo che non raccolga mai memoria. Ma questo è avanzato e un po 'pericoloso :)
FACCIO RISPOSTE FANTASTICHE il

3
Al tuo terzo punto: Erlang impone dati immutabili, quindi l'introduzione di SMP non dovrebbe compromettere la sicurezza dei thread.
nilskp,

@ nilskp, esatto, erlang è anche un linguaggio di programmazione funzionale, quindi non ci sono dati "variabili". Questo porta alla sicurezza del thread.
liuyang1

6
@nilskp: (RE: commentate il punto 3 ...) Anche se il linguaggio stesso ha un sistema di tipi immutabile, l'implementazione sottostante - passaggio di messaggi, scheduler, ecc. - è una storia completamente diversa. Il supporto SMP corretto ed efficiente non si è verificato con il semplice tocco di uno switch.
Marcelo Cantos,

@rvirding: grazie per il chiarimento addendum. Mi sono preso la libertà di integrare i tuoi punti nel corpo della mia risposta.
Marcelo Cantos,

73

Dopo qualche altra ricerca ho trovato una presentazione di Joe Armstrong.

Da Erlang - software per un mondo concorrente (presentazione) (a 13 min):

[Erlang] è un linguaggio concorrente - intendo dire che i thread fanno parte del linguaggio di programmazione, non appartengono al sistema operativo. Questo è davvero ciò che non va nei linguaggi di programmazione come Java e C ++. I thread non sono nel linguaggio di programmazione, i thread sono qualcosa nel sistema operativo e ereditano tutti i problemi che hanno nel sistema operativo. Uno dei problemi è la granularità del sistema di gestione della memoria. La gestione della memoria nel sistema operativo protegge intere pagine di memoria, quindi la dimensione più piccola che può essere un thread è la dimensione più piccola di una pagina. In realtà è troppo grande.

Se aggiungi più memoria alla tua macchina - hai lo stesso numero di bit che protegge la memoria e quindi aumenta la granularità delle tabelle di pagine - finisci per usare 64kB per un processo che conosci in esecuzione in poche centinaia di byte.

Penso che risponda se non tutte, almeno alcune delle mie domande



2
La protezione della memoria sugli stack è lì per un motivo. Erlang non protegge semplicemente le pile di diversi contesti di esecuzione tramite la MMU del processore? (E speri solo per il meglio?) Cosa succede se un thread utilizza più del suo piccolo stack? (Tutte le allocazioni dello stack sono controllate per vedere se è necessario uno stack più grande? Lo stack è mobile?)
Thanatos

2
@Thanatos: Erlang non consente ai programmi di accedere alla memoria o giocherellare con lo stack. Tutte le allocazioni devono passare attraverso il runtime gestito, sia heap che stack. In altre parole: la protezione hardware è inutile perché protegge da cose che non possono accadere comunque. Il linguaggio è puntatore-sicuro, stack-safe, memory-safe e type-safe. Un processo non può usare più del suo "stack piccolo" perché lo stack cresce secondo necessità. Puoi pensarlo come l'opposto di tiny: infinitamente grande. (Ma assegnato pigramente.)
Jörg W Mittag

4
Dovresti dare un'occhiata al sistema operativo Singularity di Microsoft Research. In Singularity, tutto il codice, il kernel, i driver di dispositivo, le librerie e i programmi utente vengono eseguiti nell'anello 0 con i privilegi del kernel completi. Tutto il codice, il kernel, i driver di dispositivo, le librerie e i programmi utente vengono eseguiti in un unico spazio di indirizzi fisico piatto senza alcuna protezione della memoria. Il team ha scoperto che le garanzie offerte dalla lingua sono molto più forti di quelle che la MMU può offrire, e allo stesso tempo l'utilizzo della MMU le ha costate fino al 30% (!!!) in termini di prestazioni. Quindi, perché usare la MMU se la tua lingua lo fa già?
Jörg W Mittag,

1
Il sistema operativo OS / 400 funziona allo stesso modo. Esiste un solo spazio di indirizzi flat per tutti i programmi. E la maggior parte delle lingue attualmente in uso ha le stesse proprietà di sicurezza (ECMAScript, Java, C♯, VB.NET, PHP, Perl, Python, Ruby, Clojure, Scala, Kotlin, Groovy, Ceylon, F♯, OCaml, the Parte "Obiettivo" di "Obiettivo-C", la parte "++" di "C ++"). Se non fosse per il codice C legacy e le funzionalità legacy di C ++ e Objective-C, non avremmo nemmeno più bisogno di memoria virtuale.
Jörg W Mittag,

47

Ho implementato coroutine in assemblatore e misurato le prestazioni.

Il passaggio tra coroutine, noti anche come processi Erlang, richiede circa 16 istruzioni e 20 nanosecondi su un moderno processore. Inoltre, spesso conosci il processo a cui stai passando (esempio: un processo che riceve un messaggio nella sua coda può essere implementato come passaggio diretto dal processo di chiamata al processo di ricezione) in modo che lo scheduler non entri in gioco, rendendo è un'operazione O (1).

Per cambiare i thread del sistema operativo, ci vogliono circa 500-1000 nanosecondi, perché stai chiamando il kernel. L'utilità di pianificazione dei thread del sistema operativo potrebbe essere eseguita in O (log (n)) o O (log (log (n))), il che inizierà a essere evidente se si hanno decine di migliaia o addirittura milioni di thread.

Pertanto, i processi Erlang sono più veloci e si adattano meglio perché entrambe le operazioni fondamentali di commutazione sono più veloci e lo scheduler viene eseguito meno spesso.


33

I processi di Erlang corrispondono (approssimativamente) a fili verdi in altre lingue; non esiste una separazione forzata del sistema operativo tra i processi. (Potrebbe esserci una separazione forzata dal linguaggio, ma questa è una protezione minore nonostante Erlang svolga un lavoro migliore rispetto alla maggior parte.) Poiché sono molto più leggeri, possono essere utilizzati molto più ampiamente.

D'altra parte, i thread del sistema operativo possono essere semplicemente programmati su diversi core della CPU e sono (principalmente) in grado di supportare l'elaborazione indipendente legata alla CPU. I processi del sistema operativo sono come thread del sistema operativo, ma con una separazione forzata del sistema operativo molto più forte. Il prezzo di queste funzionalità è che i thread del sistema operativo e (e ancora di più) i processi sono più costosi.


Un altro modo per capire la differenza è questo. Supponendo che stavi per scrivere un'implementazione di Erlang in cima alla JVM (non un suggerimento particolarmente pazzo), allora faresti in modo che ogni processo di Erlang sia un oggetto con un certo stato. Avresti quindi un pool di istanze di Thread (in genere dimensionate in base al numero di core nel tuo sistema host; questo è un parametro sintonizzabile in runtime reale Erlang BTW) che eseguono i processi Erlang. A sua volta, ciò distribuirà il lavoro da svolgere attraverso le risorse di sistema reali disponibili. È un modo piuttosto semplice di fare le cose, ma si affida completamentesul fatto che ogni singolo processo di Erlang non fa molto. Va bene ovviamente; Erlang è strutturato in modo da non richiedere che quei singoli processi siano pesanti poiché è il loro insieme generale che esegue il programma.

In molti modi, il vero problema è quello della terminologia. Le cose che Erlang chiama processi (e che corrispondono fortemente allo stesso concetto in CSP, CCS, e in particolare il calcolo π) non sono semplicemente le stesse che le lingue con un patrimonio C (inclusi C ++, Java, C # e molti altri) chiamano un processo o un thread. Ci sono alcune somiglianze (tutte implicano una nozione di esecuzione simultanea) ma sicuramente non c'è equivalenza. Quindi fai attenzione quando qualcuno ti dice "process"; potrebbero capirlo per significare qualcosa di completamente diverso ...


3
Erlang non si avvicina affatto a Pi Calculus. Il calcolo Pi presuppone eventi sincroni su canali che possono essere associati a variabili. Questo tipo di concetto non si adatta affatto al modello Erlang. Prova Join Calculus, Erlang è più vicino a questo, anche se deve ancora essere in grado di partecipare nativamente ad alcuni messaggi e quant'altro. C'era un documento di tesi (e progetto) chiamato JErlang dedicato che lo implementava.
DARE CONSIGLI TERRIBILI il

Tutto dipende da quale esattamente si visualizza il pi-calculus (e si possono modellare canali asincroni con canali sincroni più processi buffer).
Donal Fellows,

Stai solo dicendo che i processi Erlang sono leggeri ma non stai spiegando perché hanno un footprint più piccolo (sono leggeri) e perché hanno prestazioni migliori rispetto ai thread del sistema operativo.
Jonas,

1
@Jonas: per alcuni tipi di attività (in particolare le attività pesanti di calcolo) i thread del sistema operativo funzionano meglio. Intendiamoci, quelli non sono in genere compiti per i quali viene utilizzato Erlang; Erlang è focalizzato sull'avere un gran numero di semplici compiti comunicativi. Uno dei vantaggi derivanti da ciò è che nel caso di un gruppo di attività che gestiscono un pezzo di lavoro e aspettano il risultato, tutto ciò può essere fatto in un singolo thread del sistema operativo su un singolo processore, che è più efficiente di con switch di contesto.
Donal Fellows,

Teoricamente, potresti rendere un thread del sistema operativo molto economico anche usando uno stack molto piccolo e controllando attentamente il numero di altre risorse specifiche del thread allocate, ma nella pratica è abbastanza problematico. (Prevedere i requisiti dello stack è un po 'black art.) Quindi, invece, i thread del sistema operativo sono progettati in modo particolare per essere ottimali nel caso in cui ce ne siano meno (dell'ordine del numero di core della CPU) e dove stanno facendo risultati più significativi quantità di elaborazione ciascuno.
Donal Fellows

3

Penso che Jonas volesse alcuni numeri nel confrontare i thread del sistema operativo con i processi Erlang. L'autore di Programming Erlang, Joe Armstrong, qualche tempo fa ha testato la scalabilità della generazione dei processi Erlang sui thread del sistema operativo. Ha scritto un semplice server Web in Erlang e lo ha testato su Apache multi-thread (poiché Apache utilizza thread del sistema operativo). C'è un vecchio sito web con i dati risalenti al 1998. Sono riuscito a trovarlo solo una volta. Quindi non posso fornire un link. Ma l'informazione è là fuori. Il punto principale dello studio ha mostrato che Apache ha raggiunto il limite massimo di 8K processi, mentre la sua mano scritta sul server Erlang ha gestito processi 10K +.


5
Penso che tu stia parlando di questo: sics.se/~joe/apachevsyaws.html Ma ho chiesto in che modo erlang rende i thread così efficienti rispetto ai thread Kerlenl.
Jonas,

Il link @Jonas è morto. L'ultima istantanea è qui
alvaro g,

1
L'articolo diceva: "Apache muore a circa 4.000 sessioni parallele. Yaws funziona ancora con oltre 80.000 connessioni parallele".
Nathan Long,

vedere l'articolo completo su citeseerx.ist.psu.edu/viewdoc/… In effetti, si è rivelato impossibile interrompere il server Erlang utilizzando 16 macchine attaccanti, sebbene fosse facile arrestare il server Apache.
Bernhard

1

Poiché l'interprete Erlang deve solo preoccuparsi di se stesso, il sistema operativo ha molte altre cose di cui preoccuparsi.


0

uno dei motivi è che il processo erlang non viene creato nel sistema operativo, ma in evm (erlang virtual machine), quindi il costo è inferiore.

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.