Brian Kernighan spiega in questo video l'attrazione dei primi Bell Labs per i piccoli linguaggi/programmi basati su limiti di memoria
Una grande macchina sarebbe 64 k-byte – K, non M o G – e quindi questo significava che ogni singolo programma non poteva essere molto grande, e quindi c'era una tendenza naturale a scrivere programmi piccoli, e quindi il meccanismo pipe, fondamentalmente input reindirizzamento dell'output, ha permesso di collegare un programma all'altro.
Ma non capisco come ciò possa limitare l'utilizzo della memoria considerando il fatto che i dati devono essere archiviati nella RAM per essere trasmessi tra i programmi.
Da Wikipedia:
Nella maggior parte dei sistemi simili a Unix, tutti i processi di una pipeline vengono avviati contemporaneamente [enfasi mia] , con i loro flussi opportunamente collegati e gestiti dallo scheduler insieme a tutti gli altri processi in esecuzione sulla macchina. Un aspetto importante di questo, che distingue le pipe Unix dalle altre implementazioni delle pipe, è il concetto di buffering:ad esempio un programma di invio può produrre 5000 byte al secondo e un programma ricevente può essere in grado di accettare solo 100 byte al secondo, ma non i dati vengono persi. L'output del programma di invio viene invece mantenuto nel buffer. Quando il programma ricevente è pronto per leggere i dati, il programma successivo nella pipeline legge dal buffer. In Linux, la dimensione del buffer è 65536 byte (64 KB). È disponibile un filtro di terze parti open source chiamato bfr per fornire buffer più grandi, se necessario.
Questo mi confonde ancora di più, poiché vanifica completamente lo scopo dei piccoli programmi (sebbene sarebbero modulari fino a una certa scala).
L'unica cosa a cui posso pensare come soluzione alla mia prima domanda (i limiti di memoria sono problematici a seconda delle dimensioni dei dati) sarebbe che i set di dati di grandi dimensioni semplicemente non venivano calcolati all'epoca e il vero problema che le pipeline dovevano risolvere era il quantità di memoria richiesta dai programmi stessi. Ma dato il testo in grassetto nella citazione di Wikipedia, anche questo mi confonde:poiché un programma non viene implementato alla volta.
Tutto ciò avrebbe molto senso se si utilizzassero i file temporanei, ma ho capito che le pipe non scrivono su disco (a meno che non venga utilizzato lo scambio).
Esempio:
sed 'simplesubstitution' file | sort | uniq > file2
Mi è chiaro che sed
sta leggendo il file e lo sputa riga per riga. Ma sort
, come afferma BK nel video collegato, è un punto fermo, quindi tutti i dati devono essere letti in memoria (o no?), quindi vengono passati a uniq
, che (a mio avviso) sarebbe un programma di una riga alla volta. Ma tra la prima e la seconda pipe, tutti i dati devono essere in memoria, no?
Risposta accettata:
I dati non devono essere archiviati nella RAM. Le pipe bloccano i loro scrittori se i lettori non ci sono o non riescono a tenere il passo; sotto Linux (e la maggior parte delle altre implementazioni, immagino) c'è del buffering ma non è richiesto. Come accennato da mtraceur e JdeBP (vedi la risposta di quest'ultimo), le prime versioni di Unix hanno eseguito il buffering su disco, ed è così che hanno contribuito a limitare l'utilizzo della memoria:una pipeline di elaborazione potrebbe essere suddivisa in piccoli programmi, ognuno dei quali elaborerebbe alcuni dati , entro i limiti dei buffer del disco. I piccoli programmi richiedono meno memoria e l'uso di pipe significava che l'elaborazione poteva essere serializzata:il primo programma veniva eseguito, riempiva il buffer di output, veniva sospeso, quindi il secondo programma veniva programmato, elaborava il buffer, ecc. I sistemi moderni sono ordini di grandezza maggiore rispetto ai primi sistemi Unix e può far scorrere molti tubi in parallelo; ma per enormi quantità di dati vedresti comunque un effetto simile (e varianti di questo tipo di tecnica vengono utilizzate per l'elaborazione dei "big data").
Nel tuo esempio,
sed 'simplesubstitution' file | sort | uniq > file2
sed
legge i dati da file
se necessario, quindi lo scrive finché sort
è pronto a leggerlo; se sort
non è pronto, i blocchi di scrittura. I dati alla fine risiedono effettivamente nella memoria, ma questo è specifico di sort
e sort
è pronto ad affrontare qualsiasi problema (userà file temporanei se la quantità di dati da ordinare è troppo grande).
Puoi vedere il comportamento di blocco eseguendo
strace seq 1000000 -1 1 | (sleep 120; sort -n)
Questo produce una discreta quantità di dati e li convoglia a un processo che non è pronto a leggere nulla per i primi due minuti. Vedrai un numero di write
le operazioni vanno a buon fine, ma molto rapidamente seq
si fermerà e attenderà che trascorrano i due minuti, bloccato dal kernel (il write
la chiamata di sistema è in attesa).