2007/08/31(金)ファイルへのアクセス
mmapの早さを実感するべく1GBのファイルを作ってテストしてみました.
テストの内容は以下の通り.今回は読み込み限定です.
- 一度に読み出すのは1000バイト
- 3種類の読み出しをテスト
- 先頭から順に読み込む(test1)
- 後ろから順に読み込む(test2)
- 乱数で適当に読み込む(test3)
テストはメモリ4GB載ってる大学のサーバを使用.1G以上開いてるので他のプロセスの影響は無いかと.
fopen, fseek, fread
ファイルを開くときの定番です.おそらく一番楽な方法.
"rb"でファイルを開いて,目的の場所にfseekして,1000バイトfreadしてます.
ソース省略
いろいろ端折ってるけどこんな感じ.
FILE *fp; main() { fp = fopen("test.dat", "rb"); run(); fclose(fp); } void test1() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000*999; ++i){ pos = 1000 * i; fseek(fp, pos, SEEK_SET); fread(buf, sizeof(char), 1000, fp); } } void test2() { long int pos; char buf[1000]; for(int i = 1000*1000*999 -1; i > 0; --i){ pos = 1000 * i; fseek(fp, pos, SEEK_SET); fread(buf, sizeof(char), 1000, fp); } } void test3() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000; ++i){ pos = (double)rand() / RAND_MAX * 1000*1000*999; fseek(fp, pos, SEEK_SET); fread(buf, sizeof(char), 1000, fp); } }
open, lseek, read
ファイルとしてではなく,ファイルディスクリプタであつかった場合.
O_RDONLYでファイルを開いて,目的の場所にlseekして,1000バイトreadしてます.
ソース省略
いろいろ端折ってるけどこんな感じ.
int fd; main() { fd = open("test.dat", O_RDONLY); run(); close(fd); } void test1() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000; ++i){ pos = 1000 * i; lseek(fp, pos, SEEK_SET); read(fp, buf, sizeof(buf)); } } void test2() { long int pos; char buf[1000]; for(int i = 1000*1000 -1; i > 0; --i){ pos = 1000 * i; lseek(fp, pos, SEEK_SET); read(fp, buf, sizeof(buf)); } } void test3() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000; ++i){ pos = (double)rand() / RAND_MAX * 1000*1000*999; lseek(fp, pos, SEEK_SET); read(fp, buf, sizeof(buf)); } }
mmap, memcpy
最後にメモリにマップした場合.
この場合,ポインタとしてそのまま使えるので意味はないけど,一応readの代わりにmemcpyしてみました.
O_RDONLYでファイルを開いて,読み込み用で他のプロセスと共有してPROT_READ, MAP_SHAREDマッピングしてます.
で,目的の場所を配列の要素としてアクセスptr[pos]して,1000バイトコピーmemcpyしてます.
ソース省略
int fd; char *ptr; main() { fp = open("test.dat", O_RDONLY); ptr = (char*)mmap(NULL, 1000*1000*1000, PROT_READ, MAP_SHARED, fp, 0); run(); munmap(ptr, 1000*1000*1000); close(fp); } void test1() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000; ++i){ pos = 1000 * i; memcpy(buf, &ptr[pos], 1000); } } void test2() { long int pos; char buf[1000]; for(int i = 1000*1000 -1; i > 0; --i){ pos = 1000 * i; memcpy(buf, &ptr[pos], 1000); } } void test3() { long int pos; char buf[1000]; for(int i = 0; i < 1000*1000; ++i){ pos = (double)rand() / RAND_MAX * 1000*1000*999; memcpy(buf, &ptr[pos], 1000); } }
結果
数回走らせてみただけなのでどこまで信用できるかは分かりませんが.
順次アクセス | 順次アクセス(逆順) | ランダムアクセス | |
test1 | test2 | test3 | |
fseek, fread | 1.648 922 | 2.608 402 | 5.675 332 |
lseek, read | 2.332 507 | 2.567 413 | 3.238 685 |
mmap, memcpy | 1.264 184 | 0.654 710 | 1.298 496 |
mmapが圧倒的に早かったです.ページングをカーネルに任せられるのが大きそうです.
ただ,マッピングの都合上不安定になることがあるようなので,
読み込み専用で利用するとかある程度限定したほうがよさそうです.
test1, test2, test3の順に行った都合上,mmapの順次アクセス(逆順)が圧倒的に早くなってますが,
おそらくページがキャッシュされてただけだと思います*1.
ファイルへ順次アクセスするならfseekでもそれほど問題はなさそうです.
ランダムにアクセスするならlseekが良さそうです.