C++における磁盤I/O性能最適化技術

キャッシュの活用

ディスクI/Oを減らすため、メモリ上にデータを一時的に蓄えるバッファリングが有効である。C++ではstd::ostringstreamなどを用いて、複数の書き込み操作を1回のファイル出力にまとめることが可能。

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

void flushBufferToFile(const std::vector<std::string>& lines) {
    std::ostringstream buf;
    for (const auto& line : lines) {
        buf << line << '\n';
    }

    std::ofstream out("output.txt");
    out << buf.str();
}

この方法により、ファイルへのシステムコール回数が削減され、I/Oオーバーヘッドが低減される。

非同期I/O

ブロッキングを回避し、他の処理と並行してI/Oを実行するには、std::asyncやプラットフォーム固有のAPI(例:Linuxのaio_*)が利用できる。

#include <future>
#include <fstream>
#include <vector>

void backgroundWrite(const std::vector<std::string>& data) {
    std::ofstream f("data.log");
    for (const auto& s : data) f << s << '\n';
}

int main() {
    auto task = std::async(std::launch::async, backgroundWrite,
                          std::vector<std::string>{"A", "B", "C"});
    // 他の処理を実行
    task.wait(); // 書き込み完了を待機
}

ただし、標準ライブラリのstd::asyncは真の非同期I/Oではなく、スレッドによる擬似並列である点に注意が必要。

ファイルシステムの選定

ext4、XFS、NTFS、APFSなど、ファイルシステムごとにI/O特性が異なる。大容量ファイルの連続アクセスにはXFS、小ファイル多数のケースにはext4が適している場合がある。また、ログ構造ファイルシステム(如:F2FS)はSSD向けに最適化されている。

アクセスパターンの最適化

メモリマッピング(mmap)

ファイルをプロセスの仮想アドレス空間に直接マップすることで、read/writeシステムコールを回避し、メモリアクセス感覚でファイル操作が可能になる。

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("data.bin", O_RDWR);
    size_t size = 4096;
    void* addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    // メモリ操作としてファイルを読み書き
    static_cast<char*>(addr)[0] = 'X';

    munmap(addr, size);
    close(fd);
}

シーケンシャル vs ランダムアクセス

ログやストリーミングデータは順次アクセスが効率的であり、事前にposix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)を呼び出すことでカーネルにヒントを与えることができる。一方、データベースのようなランダムアクセスでは、インデックス構造やページキャッシュの活用が重要となる。

データ圧縮

ネットワーク帯域やストレージ容量がボトルネックの場合、zlibやLZ4などでデータを圧縮して書き込むことでI/O量を削減できる。ただし、CPU負荷とのトレードオフがあるため、圧縮率と処理コストを評価する必要がある。

バッチ処理とアライメント

小規模なI/Oを繰り返す代わりに、バッファに蓄積して一度に書き込むことで、システムコールのオーバーヘッドを削減できる。さらに、ブロックデバイスのセクタサイズ(通常512Bまたは4KB)に合わせてデータをアライメントすると、余分な読み取り(read-modify-write)を回避でき、特にSSDでの寿命と性能に寄与する。

struct alignas(4096) Page {
    char data[4096];
};

アプリケーションレベルのキャッシング

頻繁に参照されるファイル(設定ファイル、テンプレートなど)は、起動時にメモリに読み込んで保持することで、後続のアクセスをディスクI/Oから解放できる。LRUなどのポリシーでメモリ使用量を制御することが実用的である。

タグ: C++ I/O optimization memory mapping asynchronous I/O file system

7月22日 00:48 投稿