2013-03-26 17 views
11

Muszę pobrać jedną konkretną linię z dużego pliku (1500000 linii), wielokrotnie w pętli na wielu plikach, zadawałem sobie pytanie, co byłoby najlepsza opcja (pod względem wydajności). Istnieje wiele sposobów, aby to zrobić, męski używać tych 2Najszybszy sposób na wydrukowanie pojedynczej linii w pliku

cat ${file} | head -1 

lub

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

nie mogłem znaleźć odpowiedź na to oboje tylko pobrać pierwszy wiersz lub jedną z dwóch (lub oba) najpierw otworzyć cały plik, a następnie pobrać wiersz 1?

+2

Wykorzystanie 'oddać porównywalnych towarów do pomiaru polecenia. – choroba

+4

Dlaczego rurka 'cat' w narzędzia? Mogą otwierać pliki samodzielnie, a jeśli martwisz się wydajnością, prawdopodobnie mogą to zrobić lepiej. Ale tak, rura powinna "przesyłać strumieniowo" tylko kilka pierwszych bloków pliku (a następnie zauważyć, że konsument przestał się troszczyć). – Thilo

Odpowiedz

26

Rzuć bezużytecznego wykorzystania cat i zrobić:

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

to wyjdzie skrypt sed po wiersz został wydrukowany.


Benchmarking scenariusz:

#!/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 

Wystarczy zapisać jako benchmark.sh i uruchomić bash benchmark.sh.

. Wyniki:

head -1 file 
.001 

sed -n 1p file 
.048 

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

read line < file && echo $line 
0 

** Wyniki z pliku z 1.000.000 linii *

Więc czasy dla sed -n 1p wzrośnie liniowo wraz ze wzrostem długości pliku ale czas dla innych odmian będzie stały (i pomijalny), ponieważ wszystkie one zakończą się po przeczytaniu pierwszej linii:

enter image description here

Uwaga: czasy różnią się od oryginalnego postu ze względu na szybsze działanie systemu Linux.

+3

A może "sed 1q plik", który jest trochę mniej zajęty. – potong

+0

@potong Użyłem tego formatu, aby można było użyć do wydrukowania dowolnej pojedynczej linii w pliku. –

+1

Najlepszym rozwiązaniem jest ponowne utworzenie pliku za każdym razem. Zależnie od systemu plików buforowanie może wpływać na taktowanie, tak że pierwszy przebieg powoduje, że rzeczywiste operacje we/wy i kolejne przebiegi przynoszą korzyści. – cdarke

3

Co powiesz na unikanie rur? Zarówno sed i head obsługują nazwę pliku jako argument. W ten sposób unikasz przechodzenia przez kota. Nie mierzyłem tego, ale głowa powinna być szybsza dla większych plików, ponieważ zatrzymuje obliczenia po N liniach (podczas gdy sed przechodzi przez wszystkie z nich, nawet jeśli ich nie drukuje - chyba że poda się opcję uit zgodnie z sugestią powyżej).

Przykłady:

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

Znowu nie przetestować skuteczność.

4

Jeśli naprawdę dostajesz pierwszą linię i czytasz setki plików, rozważ wbudowane powłoki zamiast zewnętrznych zewnętrznych poleceń, użyj read, która jest powłoką wbudowaną dla basha i ksh.Eliminuje to narzut stworzenia procesowego z awk, sed, head itd

Inna sprawa robi dokładnego analizy wydajności na I/O. Przy pierwszym otwarciu, a następnie odczytaniu pliku, dane pliku prawdopodobnie nie są przechowywane w pamięci podręcznej. Jeśli jednak spróbujesz ponownie wykonać drugie polecenie dla tego samego pliku, zarówno dane, jak i i-węzeł zostały zbuforowane, więc wyniki czasowe mogą być szybsze, prawie bez względu na używane polecenie. Dodatkowo, i-węzły mogą pozostać w pamięci podręcznej praktycznie na zawsze. Robią na przykład na Solarisie. Lub w każdym razie, kilka dni.

Na przykład, linux buforuje wszystko i zlew kuchenny, co jest dobrym atrybutem wydajności. Ale powoduje to problemy z benchmarkingiem, jeśli nie jesteś świadomy problemu.

Wszystkie te "zakłócenia" efektu buforowania zależą od systemu operacyjnego i sprzętu.

Tak więc - wybierz jeden plik, przeczytaj go za pomocą polecenia. Teraz jest buforowany. Uruchamiaj to samo polecenie testowe kilkadziesiąt razy, to jest próbkowanie efektu działania polecenia i procesu potomnego, a nie sprzętu I/O.

to sed vs odczytu do 10 powtórzeń na uzyskanie pierwszej linii tego samego pliku, po odczytać pliku jednorazowo:

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

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

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

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

Jest to wyraźnie wymyślne, ale pokazuje różnicę między wbudowaną wydajnością a użyciem polecenia.

+0

+1 ładna odpowiedź. Zmieniłem swój post, aby uwzględnić użycie "czytaj" na tyle szybko, że był najszybszy (nawet nie zarejestrowałem się poza okazjonalnie 0,001). –

1

Jeśli chcesz wydrukować tylko 1 linię (słownie 20. jeden) z dużego pliku można również zrobić:

head -20 filename | tail -1 

zrobiłem „Basic” test z bash i wydaje się działać lepiej niż powyższe rozwiązanie sed -n '1{p;q}.

Test zajmuje duży plik i wypisuje linię gdzieś pośrodku (w linii 10000000), powtarza 100 razy, za każdym razem wybierając następną linię. Dlatego wybiera linia 10000000,10000001,10000002, ... i tak dalej aż 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 

Do drukowania linii z wieloma plikami

$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 
Powiązane problemy