キャッシュの活用
ディスク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などのポリシーでメモリ使用量を制御することが実用的である。