
Ich habe eine 1 TB große Datei. Ich möchte von Byte 12345678901 bis Byte 19876543212 lesen und das auf der Standardausgabe auf einem Rechner mit 100 MB RAM ausgeben.
Ich kann problemlos ein Perl-Skript schreiben, das dies tut. sysread liefert 700 MB/s (was in Ordnung ist), aber syswrite liefert nur 30 MB/s. Ich hätte gerne etwas Effizienteres, vorzugsweise etwas, das auf jedem Unix-System installiert ist und etwa 1 GB/s liefern kann.
Meine erste Idee ist:
dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))
Aber das ist nicht effizient.
Bearbeiten:
Ich habe keine Ahnung, wie ich das Syswrite falsch gemessen habe. Das hier liefert 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
und vermeidet den yes | dd bs=1024k count=10 | wc
Albtraum.
Antwort1
Dies ist aufgrund der kleinen Blockgröße langsam. Mit einem aktuellen GNU dd
(coreutils v8.16 +) ist es am einfachsten, die Optionen skip_bytes
und zu verwenden 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"
Aktualisieren
fullblock
Option oben hinzugefügt gemäß@Gilles Antwort. Zuerst dachte ich, dass es mit impliziert sein könnte count_bytes
, aber das ist nicht der Fall.
Die unten genannten Probleme sind ein potenzielles Problem. Wenn dd
Lese-/Schreibaufrufe aus irgendeinem Grund unterbrochen werden, gehen Daten verloren. Dies ist in den meisten Fällen unwahrscheinlich (die Wahrscheinlichkeit ist etwas geringer, da wir aus einer Datei und nicht aus einer Pipe lesen).
Die Verwendung von a dd
ohne die Optionen skip_bytes
und count_bytes
ist schwieriger:
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"
}
Sie können auch mit verschiedenen Blockgrößen experimentieren, aber die Gewinne werden nicht sehr dramatisch sein. Siehe -Gibt es eine Möglichkeit, den optimalen Wert für den bs-Parameter für dd zu bestimmen?
Antwort2
bs=1
weist an, dd
jeweils ein Byte zu lesen und zu schreiben. Für jeden Aufruf entsteht ein Overhead read
, write
der dies verlangsamt. Verwenden Sie für eine angemessene Leistung eine größere Blockgröße.
Wenn Sie eine ganze Datei kopieren, zumindest unter Linux, habe ich festgestellt, dasscp
und cat
sind schneller alsdd
, auch wenn Sie eine große Blockgröße angeben.
Um nur einen Teil einer Datei zu kopieren, können Sie eine Pipe tail
in verwenden head
. Dies erfordert GNU coreutils oder eine andere Implementierung, die head -c
eine bestimmte Anzahl von Bytes kopieren muss ( tail -c
ist in POSIX, aber head -c
nicht). Ein schneller Benchmark unter Linux zeigt, dass dies langsamer ist als dd
, vermutlich wegen der Pipe.
tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))
Das Problem dabei dd
ist, dasses ist nicht zuverlässig: es kann Teildaten kopieren. Soweit ich weiß, dd
ist es sicher beim Lesen und Schreiben in eine normale Datei — sieheWann ist dd zum Kopieren von Daten geeignet? (oder wann sind read() und write() partiell)- Abernur solange es nicht durch ein Signal unterbrochen wird. Mit GNU Coreutils können Sie das fullblock
Flag verwenden, es ist jedoch nicht portierbar.
Ein weiteres Problem dd
ist, dass es schwierig sein kann, eine funktionierende Blockanzahl zu finden, da sowohl die Anzahl der übersprungenen Bytes als auch die Anzahl der übertragenen Bytes ein Vielfaches der Blockgröße sein müssen. Sie können mehrere Aufrufe verwenden dd
: einen zum Kopieren des ersten Teilblocks, einen zum Kopieren der Masse der ausgerichteten Blöcke und einen zum Kopieren des letzten Teilblocks – sieheGraemes Antwortfür ein Shell-Snippet. Aber vergessen Sie nicht, dass Sie beim Ausführen des Skripts, sofern Sie das Flag nicht verwenden fullblock
, beten müssen, dass dd
alle Daten kopiert werden. dd
gibt einen Status ungleich Null zurück, wenn eine Kopie teilweise ist, sodass der Fehler leicht zu erkennen ist, es aber keine praktische Möglichkeit gibt, ihn zu beheben.
POSIX hat auf Shell-Ebene nichts Besseres zu bieten. Mein Rat wäre, ein kleines Spezialprogramm in C zu schreiben (je nachdem, was Sie genau implementieren, können Sie es dd_done_right
oder tail_head
oder nennen mini-busybox
).
Antwort3
Mit dd
:
dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes
Alternativ mit losetup
:
losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))
Und dann dd
, cat
, … das Loop-Gerät.
Antwort4
So können Sie vorgehen:
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
Das ist alles, was wirklich nötig ist - viel mehr ist nicht erforderlich. Zunächst dd count=0 skip=1 bs=$block_size1
wird lseek()
die normale Dateieingabe praktisch sofort durchgeführt. Es besteht keine Chance,verpasste Datenoder was auch immer für Unwahrheiten darüber erzählt werden, Sie können einfach direkt zu Ihrer gewünschten Startposition suchen. Da der Dateideskriptor Eigentum der Shell ist und die dd
's ihn lediglich erben, beeinflussen sie die Cursorposition und Sie können es also einfach schrittweise angehen. Es ist wirklich sehr einfach – und es gibt kein Standardtool, das für diese Aufgabe besser geeignet ist als dd
.
Dabei wird eine Blockgröße von 64 KB verwendet, die oft ideal ist. Entgegen der landläufigen Meinung beschleunigen größere Blockgrößen dd
die Arbeit nicht. Andererseits sind winzige Puffer auch nicht gut. dd
muss seine Zeit bei Systemaufrufen synchronisieren, damit es nicht warten muss, wenn Daten in den Speicher kopiert und wieder herauskopiert werden, aber auch, damit es nicht auf Systemaufrufe warten muss. Sie möchten also, dass es genug Zeit braucht, damit der nächste read()
nicht auf den letzten warten muss, aber nicht so viel, dass Sie in größeren Größen puffern als nötig.
Der erste dd
springt also zur Startposition. Das dauertnullZeit. Sie könnten an dieser Stelle jedes beliebige andere Programm aufrufen, um dessen Standardeingabe zu lesen, und es würde direkt an der gewünschten Byte-Position mit dem Lesen beginnen. Ich rufe ein anderes auf, dd
um zu lesen((interval / blocksize) -1)
Blöcke bis zur Standardausgabe zählen.
Als letztes muss noch der Modul ausgedruckt werden(wenn überhaupt)der vorherigen Divisionsoperation. Und das war's.
Glauben Sie es übrigens nicht, wenn Leute Tatsachen offen darlegen, ohne Beweise zu haben. Ja, es ist möglich, dd
eine kurze Lektüre zu machen(obwohl solche Dinge beim Lesen von einem fehlerfreien Blockgerät nicht möglich sind – daher der Name)dd
. Solche Dinge sind nur möglich, wenn Sie einen Stream, der von einem anderen als einem Blockgerät gelesen wird, nicht korrekt puffern . Beispiel:
cat data | dd bs="$num" ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size" ### correct
In beiden Fällen dd
Kopienalleder Daten. Im ersten Fall ist es möglich(obwohl unwahrscheinlich mit cat
)dass einige der Ausgabeblöcke, die dd
kopiert werden, bitgleich "$num" Bytes sind, weil dd
angegeben istnurüberhaupt etwas zu puffern, wenn der Puffer ausdrücklich in der Befehlszeile angefordert wird. bs=
stellt einemaximalBlockgröße, da dieZweckdavon dd
ist Echtzeit-E/A.
Im zweiten Beispiel gebe ich die Ausgabeblockgröße explizit an und dd
puffere Lesevorgänge, bis vollständige Schreibvorgänge durchgeführt werden können. Das hat keinen Einfluss darauf, count=
was auf den Eingabeblöcken basiert, aber dafür benötigen Sie einfach ein weiteres dd
. Alle Fehlinformationen, die Ihnen sonst gegeben werden, sollten ignoriert werden.