2013-03-26 21 views
11

Devo recuperare una riga specifica da un file grande (1500000 righe), più volte in un ciclo su più file, mi chiedevo quale sarebbe stato il migliore opzione (in termini di prestazioni). Ci sono molti modi per fare questo, ho Manly utilizzare questi 2Il modo più veloce per stampare una riga in un file

cat ${file} | head -1 

o

cat ${file} | sed -n '1p' 

non riuscivo a trovare una risposta a questa fare entrambi solo recuperano la prima linea o di uno dei due (o entrambi) per prima cosa aprire l'intero file e quindi recuperare la riga 1?

+2

Usa 'time' per misurare la comandi. – choroba

+4

Perché pipe 'cat' negli strumenti? Possono entrambi aprire i file autonomamente e, se sei preoccupato dell'efficienza, probabilmente possono farlo meglio. Ma sì, il tubo dovrebbe "trasmettere" solo i primi blocchi del file (e poi notare che il consumatore ha smesso di preoccuparsi). – Thilo

risposta

26

cadere l'uso inutile di cat e fare:

$ sed -n '1{p;q}' file 

Ciò uscire lo script sed dopo che la linea è stata stampata.


Benchmarking script:

#!/bin/bash 

TIMEFORMAT='%3R' 
n=25 
heading=('head -1 file' 'sed -n 1p file' "sed -n '1{p;q} file" 'read line < file && echo $line') 

# files upto a hundred million lines (if your on slow machine decrease!!) 
for ((j=1; j<=100,000,000;j=j*10)) 
do 
    echo "Lines in file: $j" 
    # create file containing j lines 
    seq 1 $j > file 
    # initial read of file 
    cat file > /dev/null 

    for comm in {0..3} 
    do 
     avg=0 
     echo 
     echo ${heading[$comm]}  
     for ((i=1; i<=$n; i++)) 
     do 
      case $comm in 
       0) 
        t=$({ time head -1 file > /dev/null; } 2>&1);; 
       1) 
        t=$({ time sed -n 1p file > /dev/null; } 2>&1);; 
       2) 
        t=$({ time sed '1{p;q}' file > /dev/null; } 2>&1);; 
       3) 
        t=$({ time read line < file && echo $line > /dev/null; } 2>&1);; 
      esac 
      avg=$avg+$t 
     done 
     echo "scale=3;($avg)/$n" | bc 
    done 
done 

Basta salvare come benchmark.sh ed eseguire bash benchmark.sh.

Risultati:.

head -1 file 
.001 

sed -n 1p file 
.048 

sed -n '1{p;q} file 
.002 

read line < file && echo $line 
0 

** Risultati da file con 1.000.000 linee *

Così i tempi per sed -n 1p crescerà linearmente con la lunghezza del file, ma i tempi per le altre varianti sarà costante (e trascurabile) poiché tutti si chiudono dopo aver letto la prima riga:

enter image description here

Nota: i tempi sono diversi da quelli originali, in quanto si trovano su una Linux box più veloce.

+3

O forse 'sed 1q file', che è un po 'meno occupato. – potong

+0

@potong Ho usato questo formato in modo da poter essere utilizzato per stampare qualsiasi singola riga nel file. –

+1

Idealmente dovresti ricreare il file ogni volta. A seconda del filesystem, la memorizzazione nella cache può influenzare i tempi in modo tale che la prima esecuzione abbia il vantaggio dell'I/O reale e delle successive. – cdarke

3

Che ne dici di evitare i tubi? Entrambi sed e head supportano il nome file come argomento. In questo modo eviti di passare per gatto. Non l'ho misurato, ma la testa dovrebbe essere più veloce su file più grandi in quanto interrompe il calcolo dopo N righe (mentre sed passa attraverso tutte loro, anche se non le stampa - a meno che non si specifichi l'opzione uit q come suggerito sopra).

Esempi:

sed -n '1{p;q}' /path/to/file 
head -n 1 /path/to/file 

Ancora una volta, non ho la prova l'efficienza.

4

Se si sta veramente ricevendo la prima riga e si leggono centinaia di file, quindi si considerino i builtin di shell invece dei comandi esterni esterni, utilizzare read che è una shell incorporata per bash e ksh.Questo elimina l'overhead di creazione di processo con awk, sed, head, ecc

L'altra questione sta facendo l'analisi delle prestazioni cronometrato su I/O. La prima volta che apri e poi leggi un file, i dati del file probabilmente non vengono memorizzati nella memoria. Tuttavia, se si prova nuovamente un secondo comando sullo stesso file, i dati e l'inode sono stati memorizzati nella cache, quindi i risultati a tempo potrebbero essere più veloci, praticamente indipendentemente dal comando che si utilizza. Inoltre, gli inode possono rimanere memorizzati nella cache praticamente per sempre. Lo fanno su Solaris per esempio. O comunque, diversi giorni.

Ad esempio, linux memorizza nella cache tutto e il lavello della cucina, che è un buon attributo di prestazione. Ma rende problematico il benchmarking se non si è a conoscenza del problema.

Tutte queste "interferenze" di effetto di memorizzazione nella cache dipendono dal sistema operativo e dall'hardware.

Quindi, scegli un file, leggilo con un comando. Ora è memorizzato nella cache. Esegui lo stesso comando di prova diverse decine di volte, questo è il campionamento dell'effetto del comando e della creazione del processo figlio, non il tuo hardware I/O.

questo è sed vs lettura per 10 iterazioni di ottenere la prima linea dello stesso file, dopo aver letto il file una volta:

sed: sed '1{p;q}' uopgenl20121216.lis

real 0m0.917s 
user 0m0.258s 
sys  0m0.492s 

lettura: read foo < uopgenl20121216.lis ; export foo; echo "$foo"

real 0m0.017s 
user 0m0.000s 
sys  0m0.015s 

Questo è chiaramente inventato, ma mostra la differenza tra prestazioni incorporate e utilizzo di un comando.

+0

+1 bella risposta. Ho modificato il mio post per includere l'uso di 'read' abbastanza sicuro che sia stato il più veloce (non ho nemmeno registrato oltre allo 0.001 occasionale). –

1

Se si desidera stampare solo 1 riga (diciamo il 20 ° uno) da un file di grandi dimensioni si potrebbe anche fare:

head -20 filename | tail -1 

ho fatto un test di "base" con bash e sembra un rendimento migliore rispetto la soluzione sed -n '1{p;q} sopra.

Test esegue un file di grandi dimensioni e stampa una linea da qualche parte nel mezzo (alla riga 10000000), si ripete 100 volte, selezionando ogni volta la riga successiva. Quindi seleziona linea 10000000,10000001,10000002, ... e così via fino 10000099

$wc -l english 
36374448 english 

$time for i in {0..99}; do j=$((i+10000000)); sed -n $j'{p;q}' english >/dev/null; done; 

real 1m27.207s 
user 1m20.712s 
sys  0m6.284s 

vs.

$time for i in {0..99}; do j=$((i+10000000)); head -$j english | tail -1 >/dev/null; done; 

real 1m3.796s 
user 0m59.356s 
sys  0m32.376s 

Per la stampa di una linea di più file

$wc -l english* 
    36374448 english 
    17797377 english.1024MB 
    3461885 english.200MB 
    57633710 total 

$time for i in english*; do sed -n '10000000{p;q}' $i >/dev/null; done; 

real 0m2.059s 
user 0m1.904s 
sys  0m0.144s 



$time for i in english*; do head -10000000 $i | tail -1 >/dev/null; done; 

real 0m1.535s 
user 0m1.420s 
sys  0m0.788s