Linux 非同期 I/O システム コールを使用するライブラリを作成していますio_submit
が、ext4 ファイル システムで関数のスケーリングが不十分な理由を知りたいです。可能であれば、io_submit
大きな IO 要求サイズでブロックされないようにするにはどうすればよいですか? 私はすでに次のことを行っています(ここで説明されているように):
- を使用し
O_DIRECT
ます。 - IO バッファーを 512 バイト境界に合わせます。
- バッファー サイズをページ サイズの倍数に設定します。
カーネルが でどれくらいの時間を費やすかを観察するために、 と を使用しio_submit
て 1 Gb のテスト ファイルを作成し、システム キャッシュ ( ) を繰り返しドロップして、ファイルのより大きな部分を読み取るというテストを実行しました。反復ごとに、読み取り要求が完了するまでにかかった時間と待機に費やされた時間を出力しました。カーネル バージョン 3.11 の Arch Linux を実行している x86-64 システムで、次の実験を実行しました。マシンには SSD と Core i7 CPU が搭載されています。最初のグラフは、読み取られたページ数と終了までの待機時間の関係を示しています。2 番目のグラフは、読み取り要求が完了するまでの待機に費やされた時間を表示します。時間は秒単位で測定されます。dd
/dev/urandom
sync; echo 1 > /proc/sys/vm/drop_caches
io_submit
io_submit
比較のために、 を使用して同期 IO を使用する同様のテストを作成しましたpread
。結果は次のとおりです。
非同期 IO は、約 20,000 ページの要求サイズまで期待どおりに機能しているようです。後はio_submit
ブロック。これらの観察は、次の疑問につながります。
- の実行時間が
io_submit
一定でないのはなぜですか? - この不適切なスケーリング動作の原因は何ですか?
- ext4 ファイル システム上のすべての読み取り要求を、それぞれのサイズが 20,000 ページ未満の複数の要求に分割する必要がありますか?
- この「魔法の」値である 20,000 はどこから来たのでしょうか? 自分のプログラムを別の Linux システムで実行する場合、スケーリング動作が低下することなく、使用する最大 IO 要求サイズを決定するにはどうすればよいですか?
非同期 IO のテストに使用されるコードは次のとおりです。関連があると思われる場合は、他の情報源のリストを追加できますが、関連があると思われる詳細のみを投稿しようとしました.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <iostream>
#include <memory>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
// For `__NR_*` system call definitions.
#include <sys/syscall.h>
#include <linux/aio_abi.h>
static int
io_setup(unsigned n, aio_context_t* c)
{
return syscall(__NR_io_setup, n, c);
}
static int
io_destroy(aio_context_t c)
{
return syscall(__NR_io_destroy, c);
}
static int
io_submit(aio_context_t c, long n, iocb** b)
{
return syscall(__NR_io_submit, c, n, b);
}
static int
io_getevents(aio_context_t c, long min, long max, io_event* e, timespec* t)
{
return syscall(__NR_io_getevents, c, min, max, e, t);
}
int main(int argc, char** argv)
{
using namespace std::chrono;
const auto n = 4096 * size_t(std::atoi(argv[1]));
// Initialize the file descriptor. If O_DIRECT is not used, the kernel
// will block on `io_submit` until the job finishes, because non-direct
// IO via the `aio` interface is not implemented (to my knowledge).
auto fd = ::open("dat/test.dat", O_RDONLY | O_DIRECT | O_NOATIME);
if (fd < 0) {
::perror("Error opening file");
return EXIT_FAILURE;
}
char* p;
auto r = ::posix_memalign((void**)&p, 512, n);
if (r != 0) {
std::cerr << "posix_memalign failed." << std::endl;
return EXIT_FAILURE;
}
auto del = [](char* p) { std::free(p); };
std::unique_ptr<char[], decltype(del)> buf{p, del};
// Initialize the IO context.
aio_context_t c{0};
r = io_setup(4, &c);
if (r < 0) {
::perror("Error invoking io_setup");
return EXIT_FAILURE;
}
// Setup I/O control block.
iocb b;
std::memset(&b, 0, sizeof(b));
b.aio_fildes = fd;
b.aio_lio_opcode = IOCB_CMD_PREAD;
// Command-specific options for `pread`.
b.aio_buf = (uint64_t)buf.get();
b.aio_offset = 0;
b.aio_nbytes = n;
iocb* bs[1] = {&b};
auto t1 = high_resolution_clock::now();
auto r = io_submit(c, 1, bs);
if (r != 1) {
if (r == -1) {
::perror("Error invoking io_submit");
}
else {
std::cerr << "Could not submit request." << std::endl;
}
return EXIT_FAILURE;
}
auto t2 = high_resolution_clock::now();
auto count = duration_cast<duration<double>>(t2 - t1).count();
// Print the wait time.
std::cout << count << " ";
io_event e[1];
t1 = high_resolution_clock::now();
r = io_getevents(c, 1, 1, e, NULL);
t2 = high_resolution_clock::now();
count = duration_cast<duration<double>>(t2 - t1).count();
// Print the read time.
std::cout << count << std::endl;
r = io_destroy(c);
if (r < 0) {
::perror("Error invoking io_destroy");
return EXIT_FAILURE;
}
}