Skip to content

Come fa `sì` a scrivere sul file così velocemente?

Questa è la risposta più completa che possiamo darti, ma studiala attentamente e verifica se è compatibile con il tuo progetto.

Soluzione:

in poche parole:

yes ha un comportamento simile a quello della maggior parte delle altre utilità standard che tipicamente scrivono su uno STREAM FILE con l'output bufferizzato dalla libC tramite stdio. Queste eseguono solo la chiamata di sistema write() ogni 4kb circa (16kb o 64kb) o qualunque sia il blocco di uscita BUFSIZ . echo è un write() per GNU. Questo è un lotto di commutazione di modalità (che, a quanto pare, non è costoso come un cambio di contesto).

E questo senza contare che, oltre al ciclo di ottimizzazione iniziale, yes è un semplicissimo, minuscolo ciclo C compilato e il vostro ciclo di shell non è in alcun modo paragonabile a un programma ottimizzato dal compilatore.


ma mi sbagliavo:

Quando ho detto prima che yes utilizzava stdio, ho pensato che lo facesse solo perché si comportava in modo molto simile a quelli che lo fanno. Non era corretto: emula il loro comportamento solo in questo modo. Ciò che fa in realtà è molto simile a ciò che ho fatto di seguito con la shell: per prima cosa esegue un loop per confondere i suoi argomenti (o y se non ce ne sono) fino a che non potessero più crescere senza superare BUFSIZ.

Un commento dalla fonte che precede immediatamente il relativo for afferma che:

/* Buffer data locally once, rather than having the
large overhead of stdio buffering each item.  */

yes fa il suo fa il suo write()successivamente.


digressione:

(Come originariamente incluso nella domanda e mantenuto per contestualizzare una spiegazione eventualmente informativa già scritta qui):

Ho provato timeout 1 $(while true; do echo "GNU">>file2; done;) ma non riesco a fermare il ciclo.

Il timeout è un problema che riguarda la sostituzione del comando: ora credo di averlo capito e posso spiegare perché non si ferma. timeout non si avvia perché la sua riga di comando non viene mai eseguita. La vostra shell fa un fork di una shell figlia, apre una pipe sul suo stdout e lo legge. Smetterà di leggere quando la shell figlia si chiuderà e interpreterà tutto ciò che la shell figlia ha scritto per $IFS e le espansioni glob, e con i risultati sostituirà tutto ciò che è stato scritto da $( fino alla corrispondenza ).

Ma se il figlio è un ciclo infinito che non scrive mai sulla pipe, allora il figlio non smette mai di fare il ciclo e timeoutnon viene mai completata prima che (come immagino) si faccia CTRL-C e si chiude il ciclo figlio. Quindi timeout può mai uccidere il ciclo che deve essere completato prima di poter iniziare.


altro timeouts:

... semplicemente non sono rilevanti per i problemi di prestazioni come la quantità di tempo che il programma di shell deve impiegare per passare dalla modalità utente a quella kernel per gestire l'output. timeoutperò, non è flessibile come potrebbe esserlo una shell per questo scopo: dove le shell eccellono è nella loro capacità di manipolare gli argomenti e gestire altri processi.

Come è stato notato altrove, spostando semplicemente il file [fd-num] >> named_file al target di uscita del ciclo, invece di indirizzare lì solo l'output del comando su cui si è eseguito il ciclo, può migliorare sostanzialmente le prestazioni, perché in questo modo almeno il comando open() deve essere eseguita una sola volta. Questo viene fatto anche di seguito con il comando | come output per i cicli interni.


confronto diretto:

Si potrebbe fare come:

for cmd in  exec yes 'while echo y; do :; done'
do      set +m
        sh  -c '{ sleep 1; kill "$$"; }&'"$cmd" | wc -l
        set -m
done

256659456
505401

Che è tipo come la relazione comando-sub descritta prima, ma non c'è una pipe e il figlio è in background finché non uccide il genitore. Nella relazione yes il genitore è stato effettivamente sostituito da quando è stato generato il figlio, ma la shell chiama yes sovrapponendo il proprio processo a quello nuovo, quindi il PID rimane lo stesso e il figlio zombie sa ancora chi uccidere.


buffer più grande:

Ora vediamo di aumentare il valore di write() buffer.

IFS="
";    set y ""              ### sets up the macro expansion       
until [ "${512+1}" ]        ### gather at least 512 args
do    set "[email protected][email protected]";done       ### exponentially expands "[email protected]"
printf %s "$*"| wc -c       ### 1 write of 512 concatenated "yn"'s  

1024

Ho scelto questo numero perché le stringhe di output più lunghe di 1kb venivano divise in due parti separate write()separati. Ecco di nuovo il ciclo:

for cmd in 'exec  yes' 
           'until [ "${512+:}" ]; do set "[email protected][email protected]"; done
            while printf %s "$*"; do :; done'
do      set +m
        sh  -c $'IFS="n"; { sleep 1; kill "$$"; }&'"$cmd" shyes y ""| wc -l
        set -m
done

268627968
15850496

La quantità di dati scritti dalla shell nello stesso tempo per questo test è 300 volte superiore a quella del precedente. Non male. Ma non è yes.


correlato:

Come richiesto, a questo link c'è una descrizione più approfondita dei semplici commenti sul codice.

Una domanda migliore sarebbe perché la shell scrive il file così lentamente. Qualsiasi programma compilato autonomo che utilizzi le syscall per la scrittura dei file in modo responsabile (senza scaricare ogni carattere alla volta) lo farebbe in modo ragionevolmente veloce. Quello che state facendo è scrivere righe in un file interpretato interpretato (la shell), e in aggiunta si esegue un'operazione di lotto di operazioni di input e output non necessarie. Cosa yesfa:

  • apre un file per la scrittura
  • chiama funzioni ottimizzate e compilate per
    scrivere su un flusso
  • lo stream è bufferizzato, quindi una syscall (un costoso passaggio alla modalità kernel) avviene molto raramente, in grandi pezzi
  • chiude un file

Cosa fa lo script:

  • legge una riga di codice
  • interpreta il codice, facendo molte operazioni extra per analizzare l'input e capire cosa fare.
  • per ogni iterazione del ciclo while (che probabilmente non è economico in un linguaggio interpretato):
    • chiamare la funzione date e memorizzarne l'output (solo nella versione originale - nella versione riveduta si guadagna un fattore 10 non facendolo)
    • verifica se la condizione di terminazione del ciclo è soddisfatta
    • aprire un file in modalità append
    • analizzare echo riconosce il comando (con un po' di codice di pattern matching) come un builtin di shell, chiama l'espansione dei parametri e tutto il resto sull'argomento "GNU", e infine scrive la riga nel file aperto
    • chiudere di nuovo il file
    • ripetere il processo

Le parti costose: l'intera interpretazione è estremamente costosa (bash esegue un'enorme quantità di pre-elaborazione di tutto l'input - la vostra stringa potrebbe potenzialmente contenere sostituzioni di variabili, sostituzioni di processi, espansioni di parentesi graffe, caratteri di escape e altro ancora), ogni chiamata di un builtin è probabilmente un'istruzione switch con reindirizzamento a una funzione che si occupa del builtin e, cosa molto importante, si apre e si chiude un file per ogni singola riga di output. Si potrebbe mettere >> file al di fuori del ciclo while per renderlo molto più veloce ma si è ancora in un linguaggio interpretato. Siete abbastanza fortunati che echo sia una shell integrata e non un comando esterno, altrimenti il ciclo comporterebbe la creazione di un nuovo processo (fork & exec) a ogni singola iterazione. Il che porterebbe il processo a fermarsi: si è visto quanto sia costoso quando si ha il comando date nel ciclo.

Le altre risposte hanno affrontato i punti principali. Come nota a margine, è possibile aumentare il throughput del ciclo while scrivendo sul file di output alla fine del calcolo. Confrontate:

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU" >>/tmp/f; done;

real    0m0.080s
user    0m0.032s
sys     0m0.037s

con

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU"; done>>/tmp/f;

real    0m0.030s
user    0m0.019s
sys     0m0.011s

Ti mostriamo commenti e valutazioni

Se ti piace il progetto, puoi lasciare una sezione su cosa aggiungeresti a questa dichiarazione.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.