大きなファイルの途中を読む


19

1 TBのファイルがあります。バイト12345678901からバイト19876543212に読み取り、それを100 MB RAMのマシンの標準出力に出力したいと思います。

これを行うperlスクリプトを簡単に書くことができます。sysreadは700 MB / s(これは問題ありません)を提供しますが、syswriteは30 MB / sしか提供しません。より効率的なもの、できればすべてのUnixシステムにインストールされ、1 GB /秒のオーダーで配信できるものが望ましい。

私の最初のアイデアは:

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

しかし、それは効率的ではありません。

編集:

syswriteを間違って測定した方法がわかりません。これにより、3.5 GB /秒が実現します。

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悪夢を避けます。


あなたのコマンドbs=1M iflag=skip_bytes,count_bytes
frostschutz 14年

回答:


21

ブロックサイズが小さいため、これは遅くなります。最近のGNU ddcoreutils v8.16 +)を使用して、最も簡単な方法はskip_bytescount_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@Gillesの回答に従って上記のオプションを追加しました。最初はcount_bytes、によって暗示されるかもしれないと考えましたが、そうではありません。

上記の問題は、以下の潜在的な問題ddです。何らかの理由で読み取り/書き込み呼び出しが中断されると、データが失われます。ほとんどの場合、これは起こりそうにありません(パイプではなくファイルから読み取るため、オッズは多少減少します)。


およびオプションddなしでa を使用するのはより困難です。skip_bytescount_bytes

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パラメーターの最適値を決定する方法はありますか?


@Graemeは、2番目のメソッドがbs要因でない場合に失敗しませんskipか?
スティーブンペニー

@StevenPenny、あなたが何を得ているのかわかりませんskipが、バイトではなくブロックの数です。skip_bytes最初の例で使用されている意味skip バイト単位であるため、混乱している可能性がありますか?
グレアム

あなたのbsisは4,096、その4,096バイトをより正確にスキップできないことを意味します
スティーブンペニー

1
@StevenPenny、これがブロックアライメントで開始または終了しないデータをコピーするためにdd最初と最後の使用bs=1で3つの異なる実行がある理由です。
グレアム

6

bs=1dd一度に1バイトを読み書きするように指示します。各readand write呼び出しにはオーバーヘッドがあり、これが遅くなります。適切なパフォーマンスを得るには、より大きなブロックサイズを使用します。

あなたはファイル全体をコピーすると、少なくともLinuxでは、私はそれを見つけたcpcatより速くよりもdd、あなたが大きなブロックサイズを指定した場合でも、。

あなたがパイプすることができ、ファイルの一部だけをコピーするtailhead。これには、GNU coreutilsまたはhead -c指定されたバイト数をコピーする必要がある他の実装が必要tail -cです(POSIXにはありますが、そうでhead -cはありません)。Linuxの簡単なベンチマークではdd、おそらくパイプのせいで、これがに比べて遅いことが示されています。

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

問題ddは、信頼性がないことです部分的なデータをコピーする可能性があります。私の知る限りdd、通常のファイルを読み書きする場合は安全です。ddがデータのコピーに適しているのはいつですか?を参照してください(または、read()およびwrite()partialの場合) —ただし、signalによって中断されない場合のみ。GNU coreutilsでは、fullblockフラグを使用できますが、これは移植性がありません。

もう1つの問題ddは、スキップされたバイト数と転送されたバイト数の両方がブロックサイズの倍数である必要があるため、機能するブロックカウントを見つけることが困難な場合があることです。あなたはに複数の呼び出しを使用することができdd、整列ブロックの大部分をコピーするものを第1の部分のブロックをコピーするには1と1は、最後の部分ブロックをコピーする-参照:グレアムの答えをシェルスニペットのために。ただし、fullblockフラグを使用していない限り、スクリプトを実行するときに、ddすべてのデータがコピーされるように祈る必要があることを忘れないでください。ddコピーが部分的な場合はゼロ以外のステータスを返すため、エラーを簡単に検出できますが、修復する実用的な方法はありません。

POSIXには、シェルレベルで提供するものはありません。私のアドバイスは、(あなたがそれを呼び出すことができますが、実装まさにに応じて、小さな専用のCプログラム書くことであろうdd_done_rightか、tail_headまたはmini-busybox)を。


うわー、私はyes | dd bs=1024k count=10 | wc以前問題を知らなかった。不快な。
オレ丹下

4

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))

そしてddcat、...ループデバイス。


これは非常にLinux中心のようです。AIX、FreeBSD、およびSolarisでも機能するには同じコードが必要です。
オレ丹下

0

これはあなたがこれを行うことができる方法です:

   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()、実質的に瞬時に通常のファイル入力の上。データ見逃したり、他の真実が伝えられたりする可能性はありません。目的の開始位置に直接シークできます。ファイル記述子はシェルによって所有されており、dd「」は単にそれを継承しているだけなので、それらはカーソル位置に影響を与えるので、それを段階的に実行できます。それは本当に非常に簡単です-そして、タスクより適した標準ツールはありませんdd

多くの場合理想的な64kブロックサイズを使用します。一般的な考えに反して、ブロックサイズが大きいとdd作業が速くなりません。一方で、小さなバッファも良くありません。ddシステムコールで時刻を同期して、データをメモリにコピーしてから再び出力するのを待つ必要がないようにするだけでなく、システムコールを待つ必要もありません。そのため、次のread()レコードが最後のレコードを待つ必要がないように十分な時間をかける必要がありますが、必要以上に大きなサイズでバッファリングするほどには時間がかかりません。

したがって、最初ddは開始位置にスキップします。それには時間がかかりません。その時点で気に入った他のプログラムを呼び出してそのstdinを読み取ると、目的のバイトオフセットで直接読み取りを開始できます。別のddものを呼び出して、((interval / blocksize) -1)countブロックを標準出力に読み取ります。

最後に必要なことは、前の除算操作のモジュラス(ある場合)をコピーすることです。それだけです。

ところで、人々が証拠なしに自分の顔に事実を述べるとき、それを信じないでください。はい、dd短い読み取りを行うことは可能です(ただし、健全なブロックデバイスから読み取る場合はこのようなことはできません-つまり名前)。このようなことはdd、ブロックデバイス以外から読み取られたストリームを正しくバッファリングしない場合にのみ可能です。例えば:

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

どちらの場合も、すべてのデータがddコピーされます。最初のケースでは、それは可能である(とそういえの出力ブロックのいくつかのことをするので意志アウトコピーが等しい「$ numは」ビットバイトがspec'dされるだけで、すべてで何かをバッファリングするためのバッファは、具体的にそのコマンド-上要求されたときライン。の目的はリアルタイムI / O であるため、最大ブロックサイズを表します。catddddbs=dd

2番目の例では、出力ブロックサイズを明示的に指定し、dd完全な書き込みが行われるまで読み取りをバッファリングします。count=どちらが入力ブロックに基づいているかには影響しませんが、そのためには別のものが必要ddです。そうでなければあなたに与えられたいかなる誤報も無視されるべきです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.