Ho una situazione unica in cui posso confrontare le soluzioni proposte in questa pagina e quindi sto scrivendo questa risposta come un consolidamento delle soluzioni proposte con tempi di esecuzione inclusi per ciascuna.
Impostare
Ho un file di dati di testo ASCII da 3.261 gigabyte con una coppia chiave-valore per riga. Il file contiene 3.339.550.320 righe in totale e sfida l'apertura in qualsiasi editor che ho provato, incluso il mio go-to Vim. Ho bisogno di sottoinsieme questo file al fine di indagare su alcuni dei valori che ho scoperto iniziano solo intorno alla riga ~ 500.000.000.
Perché il file ha così tante righe:
- Devo estrarre solo un sottoinsieme delle righe per fare qualcosa di utile con i dati.
- Leggere ogni riga che porta ai valori a cui tengo richiederà molto tempo.
- Se la soluzione legge oltre le righe che mi interessano e continua a leggere il resto del file, perderà tempo a leggere quasi 3 miliardi di righe irrilevanti e impiegherà 6 volte più del necessario.
Il mio scenario migliore è una soluzione che estrae solo una singola riga dal file senza leggere nessuna delle altre righe nel file, ma non riesco a pensare a come lo farei in Bash.
Ai fini della mia sanità mentale non cercherò di leggere tutte le 500.000.000 di righe di cui avrei bisogno per il mio problema. Invece cercherò di estrarre la riga 50.000.000 su 3.339.550.320 (il che significa che la lettura del file completo richiederà 60 volte più tempo del necessario).
Userò il time
built-in per confrontare ogni comando.
Baseline
Prima vediamo come head
tail
soluzione:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
La linea di base per la riga 50 milioni è 00: 01: 15.321, se fossi andato dritto per la riga 500 milioni sarebbe probabilmente ~ 12,5 minuti.
taglio
Ne dubito, ma vale la pena provare:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Questo ha richiesto 00: 05: 12.156 per l'esecuzione, che è molto più lento della linea di base! Non sono sicuro che abbia letto l'intero file o solo fino a 50 milioni prima di interrompere, ma a prescindere da ciò non sembra una soluzione praticabile al problema.
AWK
Ho eseguito la soluzione solo con il exit
perché non avrei aspettato l'esecuzione del file completo:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Questo codice è stato eseguito in 00: 01: 16.583, che è solo ~ 1 secondo più lento, ma non è ancora un miglioramento sulla base. A questo ritmo, se il comando exit fosse stato escluso, probabilmente ci sarebbero voluti circa 76 minuti per leggere l'intero file!
Perl
Ho eseguito anche la soluzione Perl esistente:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Questo codice è stato eseguito in 00: 01: 13.146, che è ~ 2 secondi più veloce della linea di base. Se lo avessi eseguito su 500.000.000 in totale, probabilmente ci sarebbero voluti circa 12 minuti.
sed
La risposta migliore alla lavagna, ecco il mio risultato:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Questo codice è stato eseguito in 00: 01: 12.705, che è 3 secondi più veloce della linea di base e ~ 0,4 secondi più veloce di Perl. Se l'avessi eseguito su tutte le 500.000.000 di file, probabilmente ci sarebbero voluti circa 12 minuti.
mapfile
Ho bash 3.1 e quindi non posso testare la soluzione mapfile.
Conclusione
Sembra che, per la maggior parte, sia difficile migliorare la head
tail
soluzione. Nella migliore delle ipotesi la sed
soluzione fornisce un aumento dell'efficienza del ~ 3%.
(percentuali calcolate con la formula % = (runtime/baseline - 1) * 100
)
Fila 50.000.000
- 00: 01: 12.705 (-00: 00: 02.616 = -3.47%)
sed
- 00: 01: 13.146 (-00: 00: 02.175 = -2,89%)
perl
- 00: 01: 15.321 (+00: 00: 00.000 = + 0,00%)
head|tail
- 00: 01: 16.583 (+00: 00: 01.262 = + 1.68%)
awk
- 00: 05: 12.156 (+00: 03: 56.835 = + 314.43%)
cut
Fila 500.000.000
- 00: 12: 07.050 (-00: 00: 26.160)
sed
- 00: 12: 11.460 (-00: 00: 21.750)
perl
- 00: 12: 33.210 (+00: 00: 00.000)
head|tail
- 00: 12: 45.830 (+00: 00: 12.620)
awk
- 00: 52: 01.560 (+00: 40: 31.650)
cut
Fila 3.338.559.320
- 01: 20: 54.599 (-00: 03: 05.327)
sed
- 01: 21: 24.045 (-00: 02: 25.227)
perl
- 01: 23: 49.273 (+00: 00: 00.000)
head|tail
- 01: 25: 13.548 (+00: 02: 35.735)
awk
- 05: 47: 23.026 (+04: 24: 26.246)
cut
awk
esed
sono sicuro che qualcuno può inventare un one-liner Perl o giù di lì;)