
我有一個 1 TB 的檔案。我想從位元組 12345678901 讀取到位元組 19876543212 並將其放在具有 100 MB RAM 的計算機上的標準輸出上。
我可以輕鬆地編寫一個 perl 腳本來執行此操作。 sysread 提供 700 MB/s(這很好),但 syswrite 只能提供 30 MB/s。我想要更有效率的東西,最好是每個 Unix 系統都安裝的東西,並且可以以 1 GB/s 的速度提供。
我的第一個想法是:
dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))
但這效率不高。
編輯:
我不知道我是如何測量 syswrite 錯誤的。這可提供 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
並避免yes | dd bs=1024k count=10 | wc
噩夢。
答案1
由於塊大小較小,因此速度很慢。使用最新的 GNU dd
(coreutils v8.16 +),最簡單的方法是使用skip_bytes
和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"
更新
fullblock
上面新增的選項@吉爾斯回答。起初我以為這可能是暗示的count_bytes
,但事實並非如此。
提到的問題是下面的一個潛在問題,如果dd
讀/寫呼叫因任何原因中斷,則資料將遺失。在大多數情況下這不太可能(因為我們是從文件而不是管道中讀取,所以幾率會降低)。
使用dd
不帶skip_bytes
andcount_bytes
選項的 a 會更困難:
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"
}
您也可以嘗試不同的區塊大小,但收益不會非常顯著。看 -有沒有辦法確定 dd 的 bs 參數的最佳值?
答案2
bs=1
告訴dd
我們一次讀取和寫入一個位元組。每個read
and呼叫都會產生開銷write
,這使得速度變慢。使用更大的塊大小以獲得良好的性能。
當你複製整個檔案時,至少在 Linux 下,我發現cp
並且cat
比dd
,即使您指定較大的區塊大小。
要僅複製文件的一部分,您可以透過管道傳輸tail
到head
.這需要 GNU coreutils 或其他一些必須head -c
複製指定數量位元組的實作(tail -c
在 POSIX 中有,但head -c
不是)。 Linux 上的快速基準測試顯示這比 慢dd
,大概是因為管道的原因。
tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))
問題dd
在於它不可靠:它可以複製部分數據。據我所知,dd
讀取和寫入常規文件時是安全的 - 請參閱dd什麼時候適合複製資料? (或者,什麼時候 read() 和 write() 是部分的)- 但只要不被訊號中斷即可。使用 GNU coreutils,您可以使用該fullblock
標誌,但這不可移植。
另一個問題dd
是很難找到有效的區塊計數,因為跳過的位元組數和傳輸的位元組數都需要是區塊大小的倍數。您可以使用多個呼叫dd
:一個用於複製第一個部分區塊,一個用於複製大部分對齊區塊,一個用於複製最後一個部分區塊 - 請參閱格雷姆的回答取得 shell 片段。但不要忘記,當您運行腳本時,除非您使用該fullblock
標誌,否則您需要祈禱它dd
會複製所有資料。dd
如果副本不完整,則傳回非零狀態,因此很容易偵測到錯誤,但沒有實際的方法來修復它。
POSIX 在 shell 層級沒有提供更好的東西。我的建議是編寫一個小型的專用 C 程式(取決於您實現的內容,您可以將其稱為dd_done_right
或tail_head
或mini-busybox
)。
答案3
和dd
:
dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes
或使用losetup
:
losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))
然後dd
,,cat
...循環設備。
答案4
您可以這樣做:
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
這就是真正需要的一切——不需要更多。首先,幾乎dd count=0 skip=1 bs=$block_size1
可以lseek()
立即超過常規文件輸入。沒有機會遺失數據或者無論有什麼其他不實說法,您都可以直接尋找您想要的起始位置。因為檔案描述符由 shell 擁有,而dd
's 只是繼承它,所以它們會影響其遊標位置,因此您可以逐步執行。它確實非常簡單——而且沒有比 更適合這項任務的標準工具了dd
。
它使用 64k 區塊大小,這通常是理想的。與普遍的看法相反,更大的區塊大小並不會使dd
工作速度更快。另一方面,微小的緩衝區也不好。dd
需要在系統呼叫中同步其時間,這樣就不需要等待將資料複製到記憶體並再次複製出來,也不需要等待系統呼叫。因此,您希望它花費足夠的時間,以便下一個read()
不必等待最後一個,但也不要太多,以免您緩衝的大小超過所需的大小。
所以第一個dd
跳到起始位置。這需要零時間。此時您可以呼叫您喜歡的任何其他程式來讀取其標準輸入,它將直接從您所需的位元組偏移開始讀取。我叫另一個人dd
來讀((interval / blocksize) -1)
計算到標準輸出的區塊數。
最後需要做的就是複製模數(如果有的話)之前的除法運算。就是這樣。
順便說一句,當人們在沒有證據的情況下在臉上陳述事實時,不要相信它。是的,可以dd
進行短讀(儘管從健康的區塊設備讀取時不可能發生這種情況 - 因此得名)。只有當您沒有正確緩衝dd
從區塊裝置以外讀取的流時,這種情況才有可能發生。例如:
cat data | dd bs="$num" ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size" ### correct
在這兩種情況下都dd
需要複製全部的數據。第一種情況是有可能的(雖然不太可能cat
)一些dd
複製出來的輸出區塊將等於“$num”字節,因為dd
是規範的僅有的當在命令列上特別請求緩衝區時,可以緩衝任何內容。bs=
代表一個最大限度塊大小因為目的ofdd
是即時I/O。
在第二個範例中,我明確指定輸出區塊大小和dd
緩衝區讀取,直到可以進行完整寫入。這不會影響count=
哪個基於輸入區塊,但為此你只需要另一個dd
.否則向您提供的任何錯誤訊息都應被忽略。