Przeczytaj środek dużego pliku

19

Mam plik 1 TB. Chciałbym czytać od bajtu 12345678901 do bajtu 19876543212 i umieścić to na standardowym wyjściu na maszynie ze 100 MB pamięci RAM.

Mogę łatwo napisać skrypt Perla, który to robi. sysread dostarcza 700 MB / s (co jest w porządku), ale syswrite dostarcza tylko 30 MB / s. Chciałbym coś bardziej wydajnego, najlepiej coś, co jest instalowane w każdym systemie Unix i może dostarczać w ilości 1 GB / s.

Mój pierwszy pomysł to:

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

Ale to nie jest skuteczne.

Edytować:

Nie mam pojęcia, jak źle oceniłem syswrite. Zapewnia to 3,5 GB / s:

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

i unika yes | dd bs=1024k count=10 | wckoszmaru.

Ole Tange
źródło
twoje polecenie zbs=1M iflag=skip_bytes,count_bytes
frostschutz

Odpowiedzi:

21

Jest to powolne ze względu na mały rozmiar bloku. Korzystając z najnowszego GNU dd( coreutils v8.16 + ), najprostszym sposobem jest użycie opcji skip_bytesi count_bytes:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

Aktualizacja

fullblockopcja dodana powyżej zgodnie z odpowiedzią @Gilles . Na początku myślałem, że może to sugerować count_bytes, ale tak nie jest.

Wspomniane problemy są potencjalnym problemem poniżej. Jeśli ddwywołania odczytu / zapisu zostaną z jakiegokolwiek powodu przerwane, dane zostaną utracone. W większości przypadków nie jest to prawdopodobne (szanse są nieco zmniejszone, ponieważ czytamy z pliku, a nie potoku).


Korzystanie ddz opcji bez skip_bytesi count_bytesjest trudniejsze:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

Możesz także eksperymentować z różnymi rozmiarami bloków, ale zyski nie będą bardzo dramatyczne. Zobacz - Czy istnieje sposób na określenie optymalnej wartości parametru bs do dd?

Graeme
źródło
@Graeme czy druga metoda się nie powiedzie, jeśli bsnie jest to czynnikiem skip?
Steven Penny
@StevenPenny, nie jestem pewien, o co ci chodzi, ale skipjest kilka bloków, a nie bajtów. Być może są zdezorientowani, ponieważ skip_bytesjest używany w pierwszym przykładzie rozumieniu skip jest w bajtach tam?
Graeme
Twój bsjest 4,096, co oznacza, że ​​nie możesz pominąć bardziej dokładnie 4,096bajtów
Steven Penny
1
@StevenPenny, dlatego istnieją trzy różne serie ddz pierwszym i ostatnim użyciem bs=1w celu skopiowania danych, które nie zaczynają się ani nie kończą przy wyrównaniu bloku.
Graeme
6

bs=1każe ddczytać i pisać jeden bajt na raz. Na każde połączenie readi writepołączenie przypada narzut , co powoduje, że jest to wolne. Użyj większego rozmiaru bloku, aby uzyskać przyzwoitą wydajność.

Kiedy kopiujesz cały plik, przynajmniej pod Linuksem, znalazłem to cpi catjest szybszy niżdd , nawet jeśli określisz duży rozmiar bloku.

Aby skopiować tylko część pliku, możesz dokonać potoku taildo head. Wymaga to GNU coreutils lub innej implementacji, która musi head -cskopiować określoną liczbę bajtów ( tail -cjest w POSIX, ale head -cnie ma). Szybki test porównawczy w systemie Linux pokazuje, że jest to wolniejsze niż dd, prawdopodobnie z powodu potoku.

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

Problem ddpolega na tym , że nie jest on wiarygodny: może kopiować częściowe dane . O ile mi wiadomo, ddjest bezpieczny podczas odczytu i zapisu do zwykłego pliku - patrz Kiedy dd nadaje się do kopiowania danych? (lub, gdy są czytane () i write () częściowe) - ale tylko tak długo, dopóki nie zostanie przerwany przez sygnał . W jądrach GNU możesz używać fullblockflagi, ale nie jest to przenośne.

Innym problemem ddjest to, że znalezienie liczby bloków, która działa, może być trudne, ponieważ zarówno liczba pominiętych bajtów, jak i liczba przesłanych bajtów musi być wielokrotnością wielkości bloku. Możesz użyć wielu wywołań do dd: jednego, aby skopiować pierwszy blok częściowy, jednego, aby skopiować większość wyrównanych bloków i jednego, aby skopiować ostatni blok częściowy - patrz odpowiedź Graeme na fragment kodu powłoki. Ale nie zapominaj, że kiedy uruchamiasz skrypt, chyba że używasz fullblockflagi, musisz się modlić, ddaby skopiować wszystkie dane. ddzwraca wartość niezerową, jeśli kopia jest częściowa, więc łatwo jest wykryć błąd, ale nie ma praktycznego sposobu na jego naprawienie.

POSIX nie ma nic lepszego do zaoferowania na poziomie powłoki. Radzę napisać mały program C specjalnego przeznaczenia (w zależności od tego, co dokładnie zaimplementujesz, możesz go nazwać dd_done_rightlub tail_headlub mini-busybox).

Gilles „SO- przestań być zły”
źródło
Wow, nigdy yes | dd bs=1024k count=10 | wcwcześniej nie znałem problemu. Paskudny.
Ole Tange
4

Z dd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

Alternatywnie z losetup:

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

A potem dd, cat... urządzenie loop.

frostschutz
źródło
To wydaje się bardzo koncentrować na Linuksie. Potrzebuję tego samego kodu do pracy w systemach AIX, FreeBSD i Solaris.
Ole Tange
0

Oto jak możesz to zrobić:

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

To wszystko, co jest naprawdę konieczne - nie wymaga wiele więcej. Po pierwsze dd count=0 skip=1 bs=$block_size1, lseek()nad regularnym wprowadzaniem plików praktycznie natychmiast. Nie ma szans na pominięcie danych lub jakiekolwiek inne nieprawdziwe informacje na ten temat, możesz po prostu szukać bezpośrednio do pożądanej pozycji początkowej. Ponieważ deskryptor pliku jest własnością powłoki, a pliki ddpo prostu dziedziczą ją, wpłyną one na jej pozycję kursora, więc możesz to zrobić krok po kroku. To naprawdę jest bardzo proste - i nie ma standardowego narzędzia, które lepiej odpowiadałoby temu zadaniu niż dd.

Wykorzystuje rozmiar bloku 64k, co często jest idealne. W przeciwieństwie do powszechnego przekonania, większe rozmiary bloków nie przyspieszają ddpracy. Z drugiej strony małe bufory też nie są dobre. ddmusi zsynchronizować swój czas w wywołaniach systemowych, aby nie musiał czekać na kopiowanie danych do pamięci i ponownie, ale także, aby nie musiał czekać na wywołania systemowe. Więc chcesz, aby zajęło to wystarczająco dużo czasu, aby następny read()nie musiał czekać na ostatni, ale nie tak bardzo, że buforujesz w większych rozmiarach niż to konieczne.

Pierwszy ddprzeskakuje do pozycji początkowej. To zajmuje zero czasu. Możesz wywołać dowolny inny program, który lubisz w tym momencie, aby odczytać jego standardowe wejście, a program zacznie czytać bezpośrednio z wybranym przesunięciem bajtu. Dzwonię dddo innego, żeby odczytać ((interval / blocksize) -1)bloki liczników na standardowe wyjście.

Ostatnią rzeczą, która jest konieczna, jest skopiowanie modułu (jeśli istnieje) z poprzedniej operacji dzielenia. I to jest to.

Nawiasem mówiąc, nie wierz w to, kiedy ludzie przedstawiają fakty na twarzy bez dowodów. Tak, możliwe jest ddwykonanie krótkiego odczytu (chociaż takie rzeczy nie są możliwe przy odczycie ze zdrowego urządzenia blokowego - stąd nazwa) . Takie rzeczy są możliwe tylko wtedy, gdy nie buforujesz poprawnie ddstrumienia odczytanego z urządzenia innego niż urządzenie blokowe. Na przykład:

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

W obu przypadkach ddskopiuj wszystkie dane. W pierwszym przypadku jest to możliwe (choć mało prawdopodobne, z cat) , że niektóre z bloków wyjściowych, które ddkopiuje OUT bit równy „$ num” Bajty ponieważ ddjest spec''d tylko do bufora w ogóle nic, gdy bufor jest wyraźnie wymagane na jej Command linia. bs=reprezentuje maksymalny rozmiar bloku, ponieważ celemdd jest we / wy w czasie rzeczywistym.

W drugim przykładzie jawnie określam wyjściowy rozmiar bloku i ddodczyty buforów, aż możliwe będzie pełne zapisanie . Nie ma to wpływu na count=to, co jest oparte na blokach wejściowych, ale do tego potrzebujesz tylko innego dd. Wszelkie dezinformacje podane w inny sposób powinny zostać zignorowane.

mikeserv
źródło