この作品は、クリエイティブ・コモンズ表示-非営利目的-共有4.0国際ライセンス契約に基づいて同じ方法でライセンスされています。
この作品(LizhaoロングによってボーエンリーZhaolongのによって作成)李Zhaolongの確認は、著作権を明記してください。
前書き
この質問はアリの側に出てきましたが、正直言って、この質問をしたところ、電話の達人だとわかりました。当然のことながら、私はこの質問に答えませんでした。実際、リマインダーがあれば、チャンスがあるかもしれません。見られなかった名詞の意味を推測するのは本当に少し面倒です。
いくつかのデータ照会の後、インタビュアーのいわゆるライトアンプリフィケーション現象は、OSによって実行されるIOの実際の数がユーザーモードによって実行されるIOの数よりも多いと言われる必要があります。これは、実際にはディスク上のファイルシステムの編成が原因です。df -T
これを使用して、現在のシステムでファイルシステム形式を表示できますext4
。通常、Linuxにはさらに多くの形式があります。
ext4ファイルシステム
上の図は、ext4ファイルシステムの基本的なアーキテクチャ図です。
を入力df
してマシン上の各ファイルシステムを表示してから、を呼び出してデータdumpe2fs -h /dev/sda10 | grep node
とinode
関連データをtune2fs -l /dev/sda10 | grep "Block size"
表示し、を呼び出してブロックサイズを表示できます。もちろん、ハードディスクの名前はマシンによって異なります。
詳細については、[4]を参照してください。ただし、これら2つの図は、ext4の一般的なアーキテクチャプロセスを理解するのに十分です。
ただし、現時点では、特定のiノード内のデータ、特にオフセットが大きいデータを検索する場合は、1回は検索できないようであり、ディスクを複数回入力する必要があることに注意する必要があります。ユーザーモードでの1つのIO操作により、オペレーティングシステムによって実際に複数のIO操作が実行される場合があります。
ディスク物理ブロックから論理ブロックへの変換については、[9]を参照してください。ext4ドキュメントは[10]を参照できます。
ライトアンプリフィケーションの再現
最初に複数の大きなファイルを生成して、このプロセスを再現してみましょう。その後、オフセットが読み取られるたびに、キャッシュは毎回同時に更新されます。簡単なコードは次のとおりです。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <iostream>
using std::string;
using std::vector;
int main(){
int FileName = 0;
vector<int> arr;
arr.reserve(500);
constexpr int len = 1024*1024*1024;
// 硬盘上没空间了,所以只生成了10个文件,想要结果更准确且机器允许的话可以生成500个向上。
for (size_t i = 0; i < 10; i++){
int fd = open(std::to_string(i).c_str(), O_RDWR | O_CREAT | O_DIRECT, 0755);
arr.push_back(fd);
char *buf;
size_t buf_size = len;
posix_memalign((void **)&buf, getpagesize(), buf_size);
int ret = write(fd, buf, len);
if(ret != len){
std::cerr << "Partially written!\n";
}
}
for(auto x : arr){
close(x);
}
return 0;
}
これらの10個のファイルをループで読み取り、毎回同期します。複数の大きなファイルをシミュレートするために、マシンはひどい生活を送っています。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <iostream>
using std::string;
using std::vector;
int main(){
vector<int> arr;
arr.reserve(500);
constexpr int len = 1024;
for (size_t i = 0; i < 10; i++){
int fd = open(std::to_string(i).c_str(), O_RDONLY | O_DIRECT, 0755);
std::cout << fd << std::endl;
arr.push_back(fd);
}
for (size_t i = 0; i < 1000; i++){
int index = i%10;
lseek(arr[index], 1024*1024*512, SEEK_SET); // 转移偏移量
char* buf;
posix_memalign((void**)&buf, getpagesize(), 1025);
std::cout << arr[index] << std::endl;
size_t buf_size = len;
int ret = read(arr[index], buf, buf_size);
if(ret != buf_size){
free(buf);
std::cerr << "Partially written!\n";
continue;
}
free(buf);
sync();
}
for(auto x : arr){
close(x);
}
return 0;
}
まず、コードでユーザーモードで1000 IO操作を実行し、読み取られたデータの合計量が1000KBであることがわかります。
コード実行時に呼び出すことができますiostat -d 1
。関数はiostatを毎秒実行することです。オペランドを指定する場合は、実行に上限を追加します。以前に追加しない場合は、上限はありません。実行回数。
オペレーティングシステムが約2500IOを実行し、データの総量が約4000KBであることがわかります。
ダイレクトIOを通常のIOプロセスに変更しました。つまりO_DIRECT
、openのパラメータを削除してから、メモリ割り当てを変更してmalloc
IOの監視を続行します。IOの
数は1200ですが、読み取られるデータの量が多くなっていることは明らかです。小さいです。40KB、10ファイル、そして上記を4回検討します。明らかにそれらの間には何らかの関係があります。個人的にはこのようだと思います。上記のext4の組織構造はすでに見てきました。ブロックが4096バイトの場合Gファイルを保存するには、保存するために二重の間接インデックスが必要です。つまり、直接IOと比較して、2つの追加IOが必要です。さらに、inodeは最初にディスク上にあり、データを読み取る必要があります。最後のデータブロックで。
tpsがまだ1000である理由は、削除しなかったためsync
です。これは、書き込まれるバイト数が非常に多い理由でもあります。削除するとsync
、ファイルデータがまだ存在するためpage cache
、読み取られたデータは次のようになります。比較的小さく、tpsも価格より安くなります。
期待に沿って。
総括する
上記の調査と[3]この記事の調査の後、Direct IOの書き込み増幅現象の原因は、ファイルシステムの実現に向けて基本的に特定できます。これは、Direct IOが異なるファイルであるたびに、必要なことを意味するためです。データの実際の保存場所を取得するために複数の追加IO操作を実行します。データはキャッシュされますが、次回は使用されないため、追加IOの数が大幅に増加します。ext4の1Gサイズのファイルでは、すでにこのひどい数の4倍です。
もちろん、より詳細な要約は[3]の最後の3つのポイントです。元のテキストが失われるのを防ぐために、ここに記録してください。
- ext3で採用されているファイルインデックス方式は、読み取りオフセットが大きい場合に追加のIOを生成し、オフセットが大きいほど、追加のIO時間が多くなります。
- Linuxはバッファキャッシュを使用してext3のインデックスブロックをキャッシュします。データブロックの場所を見つけるために、最初にインデックスブロックがキャッシュ内で検索され、キャッシュが見つからない場合はインデックスブロックがディスクから読み取られます。
- DirectIO中にLinuxカーネルによって発行されるIO要求の数は、実際には次の要因に関連しています。a)物理ディスク上の論理ブロックの連続性; b)アプリケーションバッファーの配置粒度。プログラミングでPAGE_SIZEに合わせてみてください。
実際、2つのケースでは、それぞれ読み取り側と書き込み側で、ダイレクトIOの方が適切であることがわかります。
書き込み時は、停電時のメモリデータの損失を防ぐため、すぐにディスクにデータを入れる必要があります。書き込み増幅現象の原因は、キャッシュとファイルシステムの実装にあるため、必要ありません。 WALのようなシナリオについては、ディスクインデックスが最初にキャッシュされ、その後の操作はIOにすぎないため、あまり心配する必要はありません(もちろん、バッチ書き込みと比較すると、効率は比較的低くなります)。
読み取りがデータアクセスが地域に従わないことを決定することであるとき、もちろんfadvise
それはそのようなこともしました。
結論が正しくない可能性があります。エラーを見つけた場合は指摘してください。
参照: