web-dev-qa-db-ja.com

ddをパイプバッファとして使用する適切な方法はありますか?

質問

* NIXでパイプバッファリングツールを探したところ、buffermbuffer、またはpvの使用に関する提案がありました。ただし、前の2つは常にディストリビューションの公式リポジトリ(Archなど)にあるとは限りませんが、pv(1.6.0以降)にはこの機能を妨げるバグがあります。他のいくつかの質問では、バッファとして使用されるddについての言及がありますが、ddは常に存在するため、調査したいと思います。ただし、実際に意味をなすほど精巧なものはないので、ここでは「適切な」使用方法を求めます。

言及された質問ddには https://unix.stackexchange.com/questions/345072/can-dd-be-used-to-add-a-buffer-to-a-pipe =および https://unix.stackexchange.com/questions/21918/utility-to-buffer-an-unbounded-amount-of-data-in-a-pipeline

テストを簡単にするために、私自身の実験についてのコメントとともに、以下のテストスクリプトを提供します。詳細はコード一覧の後に説明します。実行する前に、pvがインストールされており、少なくとも256Mのメモリがあることを確認してください。

#!/bin/sh

producer() {
    while [ 1 ]; do
    dd if=/dev/zero bs=64M count=1 iflag=fullblock status=none
    sleep 4
    done
}

buffer() {
    # Works, but long
    # Must at least fill 32M before consumer starts
    # So, must choose small obs and chain more to look
    # more like a proper "buffer"
    dd obs=32M status=none | \
        dd obs=32M status=none| \
        dd obs=32M status=none| \
        dd obs=32M status=none
    # Doesn't work, producer rate limited
    #dd bs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd obs=128M status=none 
    # Doesn't work, producer rate limited
    #dd ibs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd bs=128M status=none iflag=fullblock
}

consumer() {
    pv --rate-limit 1M -q | dd of=/dev/null status=none
}

producer | pv -cN produce | buffer | pv -cN consume | consumer

ここで、プロデューサーは4秒ごとに64MBのデータを生成し、128MBのバッファーを使用しますが、コンシューマーは一定の1MB /秒の速度で消費します。もちろん、これはバッファがすぐにオーバーフローすることを意味しますが、これは効果を明確に示すためです。理想的には、バッファーがいっぱいになる前に(3番目の本番で)、一定の1MB /秒の消費と、それぞれ64MBのデータを提供する本番のバーストが表示されるはずです。 「正しい」出力は次のようになります。

  produce:  128MiB 0:00:07 [   0 B/s] [  <=>                                                       ]
  consume: 7.25MiB 0:00:07 [1.01MiB/s] [       <=>                                                 ]

ここで、実用的なソリューションは次のように示されています。

dd obs=32M status=none | \
    dd obs=32M status=none| \
    dd obs=32M status=none| \
    dd obs=32M status=none

これは、必要な128MBのバッファーを4つのチャンクに分割することによって構築されます。はい、各チャンクはデータが次のレベルに渡される前にいっぱいになる必要がありますが、32MBは64MBバーストよりも小さいため、実際のバッファーであるかのようにこのテストで機能します。さて、いくつかの問題があります。

  1. 実際のアプリケーションでは、データの瞬間的なバーストがないため、チャンクは小さくする必要がありますが、小さすぎることはできません。つまり、ddコマンドの長いチェーンが存在することになります
  2. 32MBマークに達する前に、EOFが検出された場合はどうなりますか?そのブロックは失われますか?dd if=test.txt| dd obs=1M | dd obs=1M | dd of=test2.txtでテストし、結果を比較しました。これは問題ではないことがわかりました。したがって、バックアップに使用してもデータが破損することはありません。
  3. どのくらいのオーバーヘッドが発生しますか?
  4. パラメータを巧みに配置することで、同じことを実現するためのよりエレガントな方法はありますか?

コメントで説明されているように、スクリプトには他にもいくつかの試みが含まれていますが、それらは機能しません。そして、FIFO +バックグラウンドプロセスを使用してみましたが、同じ結果が得られます。

PS。ご存知のとおり、パイプのバッファリングは、AをBにバックアップする場合、特にAがシーク時間のあるHDDの場合に非常に役立ちます。だから私はこのようなことをします:

tar cSpf - <path> -C <root path> | <a large buffer> | <some parallel compressor> \
| <small buffer if compressor is generally slow and B have seek time> \
| dd bs=<several GB if B is not an SSD> iflag=fullblock oflag=direct of=<archive.tar.??>
4
Carl Dong

私は自分の答えを入れています。最高ではないかもしれませんが、大丈夫です。

注意

これは多くのテストの後に前に書かれています。

バッファリングのためにあまりにも多くのDDをチェーンしないでください。そうしないと、すべてのCPUコアがIOでブロックされ、大量のメモリが残っていてもコンピュータがフリーズします!

壊れた遅い外付けUSBドライブがあり、読み取り/書き込みにばかげたIO強度が必要な場合は、特に有毒です。

私は基本的にDDオプションのすべての組み合わせを使い果たしました。非同期IOを実行できないため、このタスクでは単一のDDは不可能のようです。それ以外の場合、DDバッファーのチェーンでは、FIFOのように機能し始める前に、最大のブロックを埋める必要があります。したがって、パイプを充填するときの初期遅延を気にしない場合は... 2つのddの作業のチェーン。他の誰かがよりエレガントなソリューションを提供できることを願っていますが、ここに使用例があります。

例1:圧縮アルゴリズムとしてXZを使用して、非常に断片化されたHDD A(応答時間ジッター)から非常に断片化されたHDD B(ジッター)へのすべてのファイルのタール化(遅い)並行して(実際にコンピューターを使用している場合はジッター)(免責事項:これは頭から書いているので、細かい部分が間違っている可能性があります。自己責任で使用してください):

tar -cpSf - -C /mnt/A . | \
  dd obs=1024M | dd obs=1024M | \
  xz -T 0 -c | \
  dd obs=1024M | dd obs=1024M | \
  dd bs=512M iflag=fullblock of=/mnt/B/A.tar.xz

速度を確認するには、pvを追加します。ここで、xzは、Aから1GBのデータが読み取られた後にのみ開始されます(1GB未満でない限り、終了します)。同様に、Bへのディスク書き込みは、1GBのデータがxzから出力された後にのみ開始されます。このコードは、tarxzの間に2GBのバッファーを、xzと書き込みの間に2GBのバッファーを提供します。最後のbs=512Mは実際には必要ありませんが、ブロックサイズが大きい(> 64M)と、特にUSBハードドライブでの平均書き込み速度が向上することがわかりました。ドライブBが使用されている場合(確認されていません)、フラグメントの作成も少ないと思います。

例2。目的:非常に断片化されたディスクAから非常に断片化されたディスクBに巨大なファイルをコピーします。

dd if=/mnt/A/file obs=<half of cache you want> | dd bs=<half of cache> iflag=fullblock oflag=direct of=/mnt/B/file

これは私が見つけることができる最も単純な形式の1つです。ファイルが十分に巨大である場合、キャッシュを埋めるために使用される最初の時間はごくわずかであるはずです。その間、非同期で読み取り/書き込みを行い、できれば十分な書き込みをグループ化して、シーケンシャルなパフォーマンスを実現します。ただし、SSDはブロックサイズを気にしないと思います。

例3。 Kamil Maciorowskiのおかげで、.zshrcに次のようになりました。

buffer() {
    if [ "$2" -gt 0 ] ; then
        dd status=none obs="$1" | buffer "$1" $(($2-1))
    else 
        cat 
    fi
}

ここで、512Mバッファーの3ブロックが必要な場合は、パイプラインでbuffer 512M 3をチェーンします。一般に、ジョブがスループットに対して十分に大きい場合(たとえば、100GB以上のデータを平均100MB /秒でコピー/圧縮する場合)、ブロックを小さくしても、パイプをより速く埋める以外に利点はありません(この時間は短いため関係ありません)。 。入れすぎるブロックが多すぎると、CPUがIOでビジー状態になり、コマンドがコンピューター全体をフリーズさせる可能性があることを確認しました。

ここで、例1は次のようになります。

tar -cpSf - -C /mnt/A . | \
buffer 1024M 2 | \
xz -T 0 -c | \
buffer 1024M 2 | \
dd bs=512M iflag=fullblock of=/mnt/B/A/tar.xz
0
Carl Dong