web-dev-qa-db-ja.com

'direct'(O_DIRECT)フラグを持つddが劇的に高速なのはなぜですか?

24台のドライブ(12台の2つのグループ)のRAID50構成のサーバーがあり、実行すると次のようになります。

dd if=/dev/zero of=ddfile2 bs=1M count=1953 oflag=direct

私は得る:

2047868928 bytes (2.0 GB) copied, 0.805075 s, 2.5 GB/s

しかし、私が実行した場合:

dd if=/dev/zero of=ddfile2 bs=1M count=1953

私は得る:

2047868928 bytes (2.0 GB) copied, 2.53489 s, 808 MB/s

O_DIRECTによってページキャッシュがバイパスされることを理解しています。しかし、私が理解しているように、ページキャッシュをバイパスすることは、基本的にmemcpyを回避することを意味します。 帯域幅ツール を使用したデスクトップでのテスト最悪の場合、14GB/sのシーケンシャルメモリ書き込み帯域幅があり、より新しいはるかに高価なサーバーでは、帯域幅はさらに優れている必要があると思います。では、なぜ余分なmemcpyが2倍以上の速度低下を引き起こすのでしょうか?ページキャッシュを使用する場合、本当にもっと多くのことが関係しますか?これは非定型ですか?

11
Joseph Garvin

_oflag=direct_の場合:

  • カーネルに、バッファーをいっぱいにしてしきい値/タイムアウトが発生するのを待つのではなく、すぐにデータを書き出す機能を提供します(つまり、データが無関係なデータの同期の背後に保持される可能性が低くなります)。
  • カーネルの作業を保存しています(ユーザーランドからカーネルへの余分なコピーはなく、ほとんどのバッファーキャッシュ管理操作を実行する必要はありません)。
  • 場合によっては、フラッシュできるよりも速くバッファをダーティすると、プログラムがダーティバッファを生成し、任意の制限へのプレッシャーが軽減されるまで待機します( SUSEの「SLES11/12サーバーでの書き込みパフォーマンスが低い大RAM " )。

より一般的には、その巨大なブロックサイズ(1メガバイト)は rAIDのブロックサイズよりも大きいため、I/Oはカーネル内で分割され、それらの小さい部分は並行して送信されます。 小さなI/Oを使用したバッファリングされたライトバックから得られる合体はあまり価値がありません( カーネルがI/Oの分割を開始する正確なポイントはいくつかの要因によって異なります 。さらに、 RAIDストライプサイズは1Mバイトより大きくなる可能性がありますが、カーネルはハードウェアRAIDについてこれを常に認識しているわけではありません。ソフトウェアRAIDの場合、カーネルはストライプサイズを最適化できる場合があります。たとえば、私が使用しているカーネルは_md0_デバイスのストライプサイズは4 Mバイトで、_/sys/block/md0/queue/optimal_io_size_)を介してそのサイズのI/Oを優先するというヒントを表します。

上記のすべてを考慮して、元のバッファリングされたコピー中に単一のCPUを最大化し、ワークロードがキャッシュ/合体の恩恵をあまり受けていないが、ディスクがより多くのスループットを処理できる場合、_O_DIRECT_コピーを実行するとカーネルのオーバーヘッドが削減されるため、ユーザースペース/サービスディスクI/Oに使用できるCPU時間が長くなります。

では、なぜ余分なmemcpyが2倍以上の速度低下を引き起こすのでしょうか?ページキャッシュを使用する場合、本当にもっと多くのことが関係しますか?

関係するのは単なる追加のmemcpyではありませんI/Oごと-維持する必要のあるすべての追加のキャッシュ機構について考えてください。ニース バッファをカーネルにコピーするのが瞬時ではないことと、ページのプレッシャーが物事を遅くする方法についての説明Linux非同期(io_submit)書き込みv/s)への回答にあります通常の(バッファリングされた)書き込み 質問。ただし、プログラムが十分な速度でデータを生成でき、CPUが過負荷になっているためにディスクに十分な速度でフィードできない場合を除いて、通常は表示されないか、問題になりません。

これは非定型ですか?

いいえ、結果は非常に典型的です使用していたワークロードの種類を使用。ただし、ブロックサイズが小さい場合(512バイトなど)は、結果が大きく異なると思います。

これを理解するのに役立つように、fioの出力のいくつかを比較してみましょう。

_$ fio --bs=1M --size=20G --rw=write --filename=zeroes --name=buffered_1M_no_fsync
buffered_1M_no_fsync: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=1
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [W(1)][100.0%][r=0KiB/s,w=2511MiB/s][r=0,w=2510 IOPS][eta 00m:00s]
buffered_1M_no_fsync: (groupid=0, jobs=1): err= 0: pid=25408: Sun Aug 25 09:10:31 2019
  write: IOPS=2100, BW=2100MiB/s (2202MB/s)(20.0GiB/9752msec)
[...]
  cpu          : usr=2.08%, sys=97.72%, ctx=114, majf=0, minf=11
[...]
Disk stats (read/write):
    md0: ios=0/3, merge=0/0, ticks=0/0, in_queue=0, util=0.00%, aggrios=0/0, aggrmerge=0/0, aggrticks=0/0, aggrin_queue=0, aggrutil=0.00%
_

したがって、バッファリングを使用して約2.1 GBytes/sで記述しましたが、そのためにCPU全体を使い果たしました。ただし、ブロックデバイス(_md0_)は、I/O(_ios=0/3_- 3つの書き込みI/Oのみ)をほとんど見なかったと言っています。これは、ほとんどのI/OがRAMにキャッシュされたことを意味します。この特定のマシンは20GバイトをRAMに簡単にバッファリングできるため、_end_fsync=1_を使用して別の実行を行い、カーネルのRAM実行の最後にキャッシュがディスクにプッシュされるため、すべてのデータが実際に不揮発性ストレージに到達するまでにかかった時間を確実に記録できます。

_$ fio --end_fsync=1 --bs=1M --size=20G --rw=write --filename=zeroes --name=buffered_1M
buffered_1M: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=1
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [F(1)][100.0%][r=0KiB/s,w=0KiB/s][r=0,w=0 IOPS][eta 00m:00s]      
buffered_1M: (groupid=0, jobs=1): err= 0: pid=41884: Sun Aug 25 09:13:01 2019
  write: IOPS=1928, BW=1929MiB/s (2023MB/s)(20.0GiB/10617msec)
[...]
  cpu          : usr=1.77%, sys=97.32%, ctx=132, majf=0, minf=11
[...]
Disk stats (read/write):
    md0: ios=0/40967, merge=0/0, ticks=0/0, in_queue=0, util=0.00%, aggrios=0/2561, aggrmerge=0/2559, aggrticks=0/132223, aggrin_queue=127862, aggrutil=21.36%
_

OK、速度は約1.9 GBytes/sに低下し、まだすべてのCPUを使用していますが、RAIDデバイスのディスクはより高速になる容量があると主張しています(_aggrutil=21.36%_)。次の直接I/O:

_$ fio --end_fsync=1 --bs=1M --size=20G --rw=write --filename=zeroes --direct=1 --name=direct_1M 
direct_1M: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=1
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [W(1)][100.0%][r=0KiB/s,w=3242MiB/s][r=0,w=3242 IOPS][eta 00m:00s]
direct_1M: (groupid=0, jobs=1): err= 0: pid=75226: Sun Aug 25 09:16:40 2019
  write: IOPS=2252, BW=2252MiB/s (2361MB/s)(20.0GiB/9094msec)
[...]
  cpu          : usr=8.71%, sys=38.14%, ctx=20621, majf=0, minf=83
[...]
Disk stats (read/write):
    md0: ios=0/40966, merge=0/0, ticks=0/0, in_queue=0, util=0.00%, aggrios=0/5120, aggrmerge=0/0, aggrticks=0/1283, aggrin_queue=1, aggrutil=0.09%
_

直接実行すると、CPUの50%弱を使用して2.2 GBytes/sを実行します(ただし、I/Oがマージされなかった方法と、はるかに多くのユーザースペース/カーネルコンテキストスイッチを実行した方法に注意してください)。システムコールごとにより多くのI/Oをプッシュする場合は、次のようになります。

_$ fio --bs=4M --size=20G --rw=write --filename=zeroes --name=buffered_4M_no_fsync
buffered_4M_no_fsync: (g=0): rw=write, bs=(R) 4096KiB-4096KiB, (W) 4096KiB-4096KiB, (T) 4096KiB-4096KiB, ioengine=psync, iodepth=1
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [W(1)][100.0%][r=0KiB/s,w=2390MiB/s][r=0,w=597 IOPS][eta 00m:00s]
buffered_4M_no_fsync: (groupid=0, jobs=1): err= 0: pid=8029: Sun Aug 25 09:19:39 2019
  write: IOPS=592, BW=2370MiB/s (2485MB/s)(20.0GiB/8641msec)
[...]
  cpu          : usr=3.83%, sys=96.19%, ctx=12, majf=0, minf=1048
[...]
Disk stats (read/write):
    md0: ios=0/4667, merge=0/0, ticks=0/0, in_queue=0, util=0.00%, aggrios=0/292, aggrmerge=0/291, aggrticks=0/748, aggrin_queue=53, aggrutil=0.87%

$ fio --end_fsync=1 --bs=4M --size=20G --rw=write --filename=zeroes --direct=1 --name=direct_4M
direct_4M: (g=0): rw=write, bs=(R) 4096KiB-4096KiB, (W) 4096KiB-4096KiB, (T) 4096KiB-4096KiB, ioengine=psync, iodepth=1
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [W(1)][100.0%][r=0KiB/s,w=5193MiB/s][r=0,w=1298 IOPS][eta 00m:00s]
direct_4M: (groupid=0, jobs=1): err= 0: pid=92097: Sun Aug 25 09:22:39 2019
  write: IOPS=866, BW=3466MiB/s (3635MB/s)(20.0GiB/5908msec)
[...]
  cpu          : usr=10.02%, sys=44.03%, ctx=5233, majf=0, minf=12
[...]
Disk stats (read/write):
    md0: ios=0/4667, merge=0/0, ticks=0/0, in_queue=0, util=0.00%, aggrios=0/292, aggrmerge=0/291, aggrticks=0/748, aggrin_queue=53, aggrutil=0.87%
_

4 Mバイトの大規模なブロックサイズでは、CPUが残っていないため、バッファリングされたI/Oは「ちょうど」2.3Gバイト/秒でボトルネックになりました(キャッシュを強制的にフラッシュしなかった場合でも)。ダイレクトI/OはCPUの約55%を使用し、3.5 GBytes/sに達することができたため、バッファードI/Oよりも約50%高速でした。

概要:I/Oパターンはバッファリングの恩恵を受けていないため(I/Oが巨大で、データが再利用されておらず、I/Oがシーケンシャルにストリーミングされている)、_O_DIRECT_が高速になるための最適なシナリオになっています。 。その背後にある元の動機については、これらを参照してください Linuxの_O_DIRECT_の元の作成者によるスライド (より長い ほとんどのスライドの埋め込みバージョンを含むPDFドキュメント )。

14
Anon