Quale è più veloce per eliminare la prima riga nel file ... sed o tail?


14

In questa risposta ( Come posso rimuovere la prima riga di un file con sed? ) Ci sono due modi per eliminare il primo record in un file:

sed '1d' $file >> headerless.txt

** ---------------- O ----------------**

tail -n +2 $file >> headerless.txt

Personalmente penso che l' tailopzione sia esteticamente più gradevole e più leggibile, ma probabilmente perché sono messo alla prova.

Quale metodo è il più veloce?


5
Non una risposta, ma una possibile considerazione è che sedè più portabile: "+2" tailfunziona bene su Ubuntu, che usa GNU tail, ma non funziona su BSD tail.
John N,

@JohnN grazie per aver condiviso la tailmancanza di compatibilità multipiattaforma.
WinEunuuchs2Unix il

3
@John N "+2" per tail funziona bene su Mac con Sierra che afferma di utilizzare il comando tail di BSD
Nick Sillito,

Urgh, hai perfettamente ragione: l'ho appena rieseguito e questa volta ho verificato l'input. Cosa che avrei dovuto fare la prima volta. È anche POSIX. / scivola via, imbarazzato.
John N,

2
@JohnN Non hai completamente torto. In passato, UNIX non forniva l' -nopzione e utilizzava la sintassi tail +2 $file. Vedi freebsd.org/cgi/… È possibile che stavi pensando a questo piuttosto che a uno dei moderni BSD.
hvd,

Risposte:


28

Prestazioni di sedvs. tailper rimuovere la prima riga di un file

TL; DR

  • sed è molto potente e versatile, ma questo è ciò che lo rende lento, specialmente per file di grandi dimensioni con molte linee.

  • tail fa solo una cosa semplice, ma quella lo fa bene e velocemente, anche per file più grandi con molte linee.

Per file di piccole e medie dimensioni sede tailprestazioni altrettanto veloci (o lente, a seconda delle aspettative). Tuttavia, per file di input più grandi (più MB), la differenza di prestazioni aumenta in modo significativo (un ordine di grandezza per i file nell'intervallo di centinaia di MB), con tailprestazioni nettamente superiori sed.

Sperimentare

Preparazioni generali:

I nostri comandi da analizzare sono:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

Si noti che sto eseguendo il piping dell'output /dev/nullogni volta per eliminare l'output del terminale o le scritture di file come collo di bottiglia delle prestazioni.

Configuriamo un disco RAM per eliminare l'I / O del disco come potenziale collo di bottiglia. Personalmente ne ho tmpfsmontato uno , /tmpquindi ho semplicemente messo il mio testfilelì per questo esperimento.

Quindi una volta sto creando un file di test casuale contenente una determinata quantità di linee $numoflinescon lunghezza di linea casuale e dati casuali usando questo comando (nota che non è assolutamente ottimale, diventa molto lento per circa> 2 M di linee, ma a chi importa, non è il cosa che stiamo analizzando):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

Oh, a proposito. il mio portatile di prova esegue Ubuntu 16.04, 64 bit su una CPU Intel i5-6200U. Solo per confronto.

Timing di file di grandi dimensioni:

Impostare un enorme testfile:

L'esecuzione del comando sopra ha numoflines=10000000prodotto un file casuale contenente 10 milioni di righe, che occupa un po 'più di 600 MB - è piuttosto enorme, ma iniziamo con esso, perché possiamo:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Esegui la corsa a tempo con il nostro enorme testfile:

Ora facciamo solo una singola corsa cronometrata con entrambi i comandi per stimare prima con quale grandezza stiamo lavorando.

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

Vediamo già un risultato davvero chiaro per i file di grandi dimensioni, tailè di una grandezza più veloce di sed. Ma solo per divertimento e per essere sicuri che non ci siano effetti collaterali casuali che fanno una grande differenza, facciamolo 100 volte:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

La conclusione rimane la stessa, sedè inefficiente per rimuovere la prima riga di un file di grandi dimensioni, taildovrebbe essere utilizzata lì.

E sì, so che i costrutti del ciclo di Bash sono lenti, ma stiamo facendo solo relativamente poche iterazioni qui e il tempo impiegato da un ciclo normale non è significativo rispetto ai sed/ tailruntime comunque.

Timing di file di piccole dimensioni:

Impostare un piccolo testfile:

Ora per completezza, diamo un'occhiata al caso più comune che hai un piccolo file di input nell'intervallo kB. Creiamo un file di input casuale con numoflines=100, simile al seguente:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

Esegui la corsa a tempo con il nostro piccolo testfile:

Poiché possiamo aspettarci che i tempi per file così piccoli siano nel giro di pochi millisecondi per esperienza, facciamo subito 1000 iterazioni:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

Come puoi vedere, i tempi sono abbastanza simili, non c'è molto da interpretare o meravigliarsi. Per file di piccole dimensioni, entrambi gli strumenti sono ugualmente adatti.


+1 per la risposta grazie. Ho modificato la domanda originale (scusate) basandomi sul commento di Serg che awkpuò fare anche questo. La mia domanda originale era basata sul collegamento che ho trovato in primo luogo. Dopo tutto il tuo duro lavoro, ti preghiamo di avvisare se dovrei rimuovere awkcome candidato soluzione e riportare l'attenzione sull'ambito del progetto originale di solo sede tail.
WinEunuuchs2Unix

Che sistema è questo? Sul mio mac (quindi strumenti BSD), testare su / usr / share / dict / words mi dà 0,09 s per sed e 0,19 s per coda (e awk 'NR > 1', cosa interessante).
Kevin,

5

Ecco un'altra alternativa, usando solo builtin bash e cat:

{ read ; cat > headerless.txt; } < $file

$fileviene reindirizzato nel { }raggruppamento di comandi. La readsemplice legge e scarta la prima linea. Il resto dello stream viene quindi reindirizzato nel catquale lo scrive nel file di destinazione.

Sul mio Ubuntu 16.04 le prestazioni di questo e la tailsoluzione sono molto simili. Ho creato un file di prova di grandi dimensioni con seq:

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail soluzione:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/ soluzione di rinforzo:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

Al momento ho solo una macchina virtuale Ubuntu a portata di mano e ho visto variazioni significative nei tempi di entrambi, sebbene siano tutti nello stesso campo di gioco.


1
+1 per la risposta grazie. Questa è una soluzione molto interessante e adoro le parentesi graffe e la lettura da destra a sinistra tramite l'ordine gerarchico di bash. (non sono sicuro di averlo scritto correttamente). È possibile aggiornare la tua risposta con le dimensioni del file di input e i risultati dei benchmark temporali se è abbastanza facile da fare?
WinEunuuchs2Unix il

@ WinEunuuchs2Unix I tempi sono stati aggiunti, anche se non sono molto affidabili poiché si trovano su una macchina virtuale. Non ho un'installazione Ubuntu in metallo a portata di mano in questo momento.
Trauma digitale il

Non credo che VM vs Bare Metal contino quando si confrontano comunque VM con VM. Grazie per la prova dei tempi. Probabilmente ci andrei, tailma penso ancora che l' readopzione sia molto bella.
WinEunuuchs2Unix il

4

Provando sul mio sistema e prefisso ogni comando con timeho ottenuto i seguenti risultati:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

e coda:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

il che suggerisce che, sul mio sistema almeno AMD FX 8250 con Ubuntu 16.04, la coda è significativamente più veloce. Il file di test aveva 10.000 righe con una dimensione di 540k. Il file è stato letto da un HDD.


+1 per la risposta grazie. In un test separato in AU Chatroom un utente ha mostrato che la coda è 10 volte più veloce (2,31 secondi) rispetto a sed (21,86 secondi) usando un RAMDisk con file da 61 MB. Ho modificato la tua risposta per applicare i blocchi di codice ma potresti volerlo modificare anche con le dimensioni del file che hai usato.
WinEunuuchs2Unix il

@Serg Assolutamente giusto che questa è solo una risposta aneddotica, e potenzialmente potresti ottenere risultati diversi con diverse configurazioni hardware, diversi file di test ecc.
Nick Sillito,

2
Il file che non si trova nella cache, quando si utilizza sedpotrebbe giocare un fattore in questo risultato, è l'ordine in cui li hai testati.
Minix

che tipo di sistema? Come ho commentato un altro post qui, sul mio mac sedera circa il doppio più veloce.
Kevin,

1

Non esiste un modo oggettivo per dire quale sia la migliore, perché sede tailnon sono le uniche cose che girano su un sistema durante l'esecuzione del programma. Molti fattori come l'I / O del disco, l'I / O di rete, la CPU si interrompono per i processi con priorità più alta - tutti questi fattori influenzano la velocità di esecuzione del programma.

Entrambi sono scritti in C, quindi questo non è un problema linguistico, ma più ambientale. Ad esempio, ho SSD e sul mio sistema questo richiederà tempo in microsecondi, ma per lo stesso file sul disco rigido ci vorrà più tempo perché gli HDD sono significativamente più lenti. Quindi anche l'hardware ha un ruolo in questo.

Ci sono alcune cose che potresti voler tenere a mente quando consideri quale comando scegliere:

  • Qual'e 'il tuo scopo ? sedè un editor di stream per trasformare il testo. tailserve per produrre righe di testo specifiche. Se si desidera gestire le linee e stamparle solo, utilizzare tail. Se si desidera modificare il testo, utilizzare sed.
  • tailha una sintassi molto più semplice di sed, quindi usa ciò che puoi leggere e ciò che gli altri possono leggere.

Un altro fattore importante è la quantità di dati che stai elaborando. I piccoli file non ti daranno alcuna differenza di prestazioni. L'immagine diventa interessante quando hai a che fare con file di grandi dimensioni. Con un BIGFILE.txt da 2 GB, possiamo vedere che sedha molte più chiamate di sistema rispetto a tail, ed è molto più lento.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total

+1 per la risposta grazie. Ma non sono sicuro che questo commento mi aiuti a decidere quale comando dovrei usare ...
WinEunuuchs2Unix

@ WinEunuuchs2Unix Bene, hai chiesto quale comando è migliore, quindi sto rispondendo esattamente a quella domanda. Quale comando scegliere dipende da te. Se riesci a leggere tailmeglio di sed- usalo. Personalmente userei pythono awkpiuttosto che sedperché può diventare complesso. Inoltre, se sei preoccupato per le prestazioni, affrontiamo la realtà: qui stai vedendo risultati in microsecondi. Non sentirai la differenza a meno che non si tratti di un file enorme in un intervallo di gigabyte che stai cercando di leggere
Sergiy Kolodyazhnyy,

Oh, apprezzerei anche una awkrisposta:) ... La mia domanda si basava su un'altra AU Q&A (nel link) e lì non hanno mai menzionato awk. Sono d'accordo che la differenza di tempo è nominale su file di piccole dimensioni. Stavo solo cercando di sviluppare alcune buone abitudini.
WinEunuuchs2Unix il

1
@ WinEunuuchs2Unix Certo, qui si tratta di: awk 'NR!=1' input_file.txt . Mi dà lo stesso risultato, circa 150 millisecondi, lo stesso numero per entrambi taile sed. Ma agian, sto usando SSD, quindi direi che è il disco rigido e la CPU che contano, non il comando.
Sergiy Kolodyazhnyy,

1
@Serg anche con solo un file di 60 MB contenente 1M di righe, 1000 corse sedimpiegano molto più di 3 minuti, mentre tailrichiedono solo circa 20 secondi. Non è poi così grande, in realtà, sicuramente non nella gamma GB.
Byte Commander

1

La risposta migliore non ha preso in considerazione il disco > /dev/null

se si dispone di un file di grandi dimensioni e non si desidera creare un duplicato temporaneo sul disco, provare vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

Modifica: se il file è più grande della memoria disponibile vim -cnon funziona, sembra che non sia abbastanza intelligente da eseguire un caricamento incrementale del file


0

Altre risposte mostrano bene cosa è meglio creare un nuovo file con la prima riga mancante. Se si desidera modificare un file invece di creare un nuovo file, scommetto che edsarebbe più veloce perché non dovrebbe assolutamente creare un nuovo file. Ma devi cercare come rimuovere una linea con edperché l'ho usata solo una volta.

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.