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);
	}
}

結果

数回走らせてみただけなのでどこまで信用できるかは分かりませんが.

順次アクセス順次アクセス(逆順)ランダムアクセス
test1test2test3
fseek, fread1.648 9222.608 4025.675 332
lseek, read2.332 5072.567 4133.238 685
mmap, memcpy1.264 1840.654 7101.298 496

mmapが圧倒的に早かったです.ページングをカーネルに任せられるのが大きそうです.
ただ,マッピングの都合上不安定になることがあるようなので,
読み込み専用で利用するとかある程度限定したほうがよさそうです.

test1, test2, test3の順に行った都合上,mmapの順次アクセス(逆順)が圧倒的に早くなってますが,
おそらくページがキャッシュされてただけだと思います*1

ファイルへ順次アクセスするならfseekでもそれほど問題はなさそうです.
ランダムにアクセスするならlseekが良さそうです.

*1 : 試せばいいだけなんだろうけど,なんかめどい