ディスクブロックサイズの倍数で読み取り/書き込み操作を実行しないと、パフォーマンスが低下するのはなぜですか?

ディスクブロックサイズの倍数で読み取り/書き込み操作を実行しないと、パフォーマンスが低下するのはなぜですか?

恋に落ちたLinuxシステムプログラミング、3章バッファリングされたI / O

ブロックは、ファイルシステムの最小ストレージ単位を表す抽象化であることを第1章で思い出してください。カーネル内部では、すべて ファイルシステム操作はブロック単位で行われます。。実際、ブロックはI / Oの普遍的な言語です。したがって、ブロックサイズよりも小さい、またはブロックサイズの倍数ではないデータ量に対してI / O操作を実行することはできません。シングルバイトだけを読みたいのは残念です。ブロック全体を読む必要があります。 4.5ブロックのデータを書きたいですか? 5つのブロックを書く必要があります。つまり、ブロック全体を読み取り、変更された半分のみを更新してからブロック全体を書き換えることを意味します。

これがどこに行くのかがわかります。部分ブロック操作は非効率的です。オペレーティングシステムは、すべてがブロックソート境界で発生し、次に最大のブロックに丸められるようにして、I / Oを「修正」する必要があります。残念ながら、これは一般にユーザースペースアプリケーションを作成する方法ではありません。ほとんどのアプリケーションは、サイズがブロックサイズに依存しないフィールドや文字列など、より高いレベルの抽象化の面で動作します。最悪の場合、ユーザースペースアプリケーションは一度に1バイトしか読み書きできません!これは途方もない無駄です。各シングルバイト書き込みは実際にブロック全体を書き込みます。

ユーザーバッファI/O

通常のファイルに対して多くの小さなI / O要求を発行する必要があるプログラムは、しばしばユーザーバッファI / Oを実行します。これは、カーネルによって実行されるバッファリングとは異なり、アプリケーションによって手動で、またはライブラリから透過的にユーザー空間で実行されるバッファリングを表します。第2章で説明したように、パフォーマンス上の理由からカーネルは書き込みを延期し、隣接するI / O要求を統合し、事前に読み取ってデータを内部的にバッファリングします。。さまざまな方法でユーザーバッファリングもパフォーマンスを向上させるように設計されています。

ユーザー空間プログラムddを使用する例を考えてみましょう。

dd bs=1 count=2097152 if=/dev/zero of=pirate

bs = 1パラメータのため、このコマンドはデバイス/ dev / zero(無制限のゼロストリームを提供する仮想デバイス)からファイルの不正複製として2,097,152個のシングルバイトブロックに2 MBをコピーします。つまり、約200万個でデータをコピーします。読み書き操作- 一度に1バイトです。

これで同じ2MBのコピーが考慮されますが、1,024バイトのブロックを使用します。

dd bs=1024 count=2048 if=/dev/zero of=pirate

これは同じ2MBを同じファイルにコピーしますが、問題は1,024倍少なくなります。読み書き操作。表 3-1 に示すように、パフォーマンスの向上は膨大です。ここでは、ブロックサイズのみが異なる4つのddコマンドで測定された時間(3つの異なる測定値を使用)を記録します。リアルタイムは総ウォールクロック時間、ユーザー時間はユーザー空間でプログラムコードを実行するのに費やされた時間、システム時間はカーネル空間でシステムコールを実行するプロセスに費やされた時間を表します。

表 3-1.ブロックサイズがパフォーマンスに与える影響

Block size Real time User time System time

1 byte 18.707 seconds 1.118 seconds 17.549 seconds
1,024 bytes 0.025 seconds 0.002 seconds 0.023 seconds
1,130 bytes 0.035 seconds 0.002 seconds 0.027 seconds

1,024バイトブロックを使用すると、シングルバイトブロックと比較してパフォーマンスが大幅に向上します。しかし、表にも記載されています。ディスクブロックサイズの倍数で操作を実行しない場合は、より大きなブロックサイズ(システムコール数が少ない)を使用するとパフォーマンスが低下する可能性があります。少ない呼び出しが必要ですが、1,130バイトの要求は最終的にソートされていない要求を生成するため、1,024バイトの要求よりも効率が悪くなります。

これらのパフォーマンス向上を活用するには、物理​​ブロックサイズに関する事前の知識が必要です。表の結果は、ブロックサイズが1,024、1,024の倍数、または1,024の除数である可能性があることを示しています。 /dev/zeroの場合、ブロックサイズは実際には4,096バイトです。

  1. 「より少ない数の呼び出しが必要にもかかわらず、1,130バイトの要求が最終的にソートされていない要求を生成し、1,024バイトの要求よりも効率が悪い」理由は何ですか? (1024バイトの要求でパフォーマンスが異なるのはなぜですか?)

    • 発行されたシステムコールcountの数との割合は何ですか?bsdd

    • 「読み書き操作」の回数はどのように決定されますか?

  2. 「カーネルが書き込みスモーク、隣接I / O要求の統合、および事前読み取りを介して内部でデータをバッファリングする」場合、ユーザーバッファが必要なのはなぜですか?カーネルバッファはすでにユーザバッファが何をしているのですか?

  3. 「ファイルシステムジョブがブロックで発生する」とは、ジョブがブロックまたはブロックの整数倍で発生することを意味しますか?

ありがとうございます。

答え1

「より少ない数の呼び出しが必要にもかかわらず、1,130バイトの要求が最終的にソートされていない要求を生成し、1,024バイトの要求よりも効率が悪い」理由は何ですか?

概念的なモデルを提供します。この問題を軽減するいくつかの最適化がカーネルに存在する可能性があります(ただし完全には消えません)。

ブロックサイズが1024の場合、一連のブロックが生成されます。

[1, 1024], [1025, 2048], [2049, 3076], [3077, 4096], ...

1130のブロックサイズが記録されると、write()システムコールへの最初の呼び出しは、1つの要求を満たすために2つのディスクブロックを作成する必要があります。まず、最初の1024バイトをブロックに書き込み、[1, 1024]106バイトを書き込まないままにします。次に、2番目のブロック([1025, 2048])を読み取り、残りの106バイトをブロックの最初の106バイトにコピーし、ブロックをディスクに書き戻します。

システムコールへの次の呼び出しは、2番目のブロックを再度読み込みwrite()[1025, 2048])、ブロックのバイトに書き込む1130バイトのうち最初の918バイト(1024-106)をコピーしてから、ブロックを[1131, 2048]ディスクに再コピーする必要があります。次に、3番目のブロック([2049, 3076])を読み取り、1130の最後の212バイトをブロックの最初の212バイトに書き込み、そのブロックをディスクに書き戻します。

このパターンは続きます。少数の呼び出しにもかかわらず、write()カーネルは既存のブロックに単に書き込むのではなく、既存のブロックを繰り返し読み書き/更新/書き込みする必要があります。

sをブロックサイズに合わせると、write()「チャンクの読み取り、一部の更新、書き換え」の状況は発生せず、チャンクを書き込んで続行でき、読み取り/更新する必要はありません。ペアを満たすための同じチャンクwrite()

「カーネルが書き込みスモーク、隣接I / O要求の統合、および事前読み取りを介して内部でデータをバッファリングする」場合、ユーザーバッファが必要なのはなぜですか?カーネルバッファはすでにユーザバッファが何をしているのですか?

ユーザー空間はカーネル空間バッファに直接アクセスできません。ユーザー空間バッファは、プログラムがすべてのバイトに対してシステムコールを行わずに「チャンク」を読み取ることができるようにするために必要です(Loveが示すように非効率的です)。

「ファイルシステムジョブがブロックで発生する」とは、ジョブがブロックまたはブロックの整数倍で発生することを意味しますか?

ストレージデバイスとの通信に使用されるデバイスとプロトコルによって異なると思います。

関連情報