前回、mmap(2)というシステムコールを使って、ファイルコピーの速度を上げる方法を解説しました。このシステムコールは大きな問題を起こすこともないので、使いやすく、実際に多くの開発者が利用しています。今回は、プログラムが使用した物理メモリサイズやページフォールトの回数、コンテキストスイッチの回数などを調べて、mmap(2)の性格を分析していきます(編集部)
前回、ファイルをコピーするためのシステムコールの一種である「mmap(2)」を紹介した。このシステムコールを使えば、「read(2)」や「write(3)」といったシステムコールを使うよりも高速に動作するファイルコピープログラムを作れる。処理にかかった平均時間を比較したところ、mmap(2)を利用したプログラムは、read(2)/write(2)を使ったプログラムの4〜5倍高速に動作していた。
mmap(2)はプログラムの処理性能を引き上げることを考えた時に、多くの開発者が思い付き、実際に利用しているシステムコールだ。経験を積んだ開発者にとっては「おなじみの」システムコールと言えるかもしれない。UNIX系OSを構成するプログラムの中にも、活用しているものが多い。
前回は、mmap(2)を利用したファイルコピープログラムをの動きをtruss(1)コマンドで調べ、どのようなシステムコールをどれくらい頻繁に呼び出しているのかを調べた。今回は「time(1)」というコマンドも使って、違う側面からmmap(2)の性格を調べてみる。
サンプルプログラムの作成には、連載「いまさら聞けないVim」の第8回で紹介した環境を利用する。OSによるコマンドの違いなどは本連載の第2回にまとめてあるので、こちらも参考にしていただきたい。
今回使用するサンプルプログラムのソースコードを見ていこう。まずは標準ライブラリ関数である「fgetc(3)」と「fputc(3)」を使ったプログラム(copy-fgetc-fputc.c)だ。前回用意したものとまったく同じものである。関数fgetc(3)とfputc(3)は最終的にシステムコールread(2)/write(2)を呼び出す。シンプルで、かつ、それなりに高速に動作し、メモリもあまり消費しないプログラムだ。
#include <stdio.h> int main(void) { FILE *fi, *fo; int b; fi = fopen("in", "r"); fo = fopen("out", "w"); b = fgetc(fi); while (EOF != b) { fputc(b, fo); b = fgetc(fi); } }
続いて、システムコールmmap(2)を使ったコピープログラム(copy-mmap.c)を用意した。これも、前回用意したプログラムと同じものだ。mmap(2)を実行すると、read(2)/write(3)を使わなくとも、メモリにマッピングしたものをコピーするだけでファイルをコピーできる。サンプルのソースコードをなるべく短く、シンプルにするために、入力用に「in」というファイル、出力用に「out」というファイルがすでに存在していることを前提としている。実用のプログラムで利用するには、ファイルを用意する部分なども実装する必要があるが、そこまで用意するとソースコードが込み入ったものになるので、あらかじめテスト用ファイルが存在する前提でサンプルコードを作らせてもらった。
#include <unistd.h> #include <fcntl.h> #include <sys/mman.h> int main(void) { int fdi, fdo, fsize = 104857600; char *i, *o; fdi = open("in", O_RDONLY); fdo = open("out", O_RDWR); i = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fdi, 0); o = mmap(NULL, fsize, PROT_WRITE, MAP_SHARED, fdo, 0); while (fsize--) { *o++ = *i++; } }
最後に用意したサンプルプログラムは、標準ライブラリ関数memcpy(3)を利用したものだ(copy-mmap2.c)。memcpy(3)はライブラリ関数であって、システムコールではないのだが、コンパイル時にコンパイラが最適なバイナリコードに置き換えるため、ファイルをとても速くコピーできるという特性を持っている。実用の面から見ても有益なので、覚えておきたいライブラリ関数だ。
#include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> int main(void) { int fdi, fdo, fsize = 104857600; char *i, *o; fdi = open("in", O_RDONLY); fdo = open("out", O_RDWR); i = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fdi, 0); o = mmap(NULL, fsize, PROT_WRITE, MAP_SHARED, fdo, 0); memcpy(o, i, fsize); }
以上、用意した3つのソースコードは次のようにコンパイルすれば良い。
clang -o 出力ファイル名 ソースコード.c
3種類とも、100Mbytesの一時ファイルをコピーするプログラムなので、大きさ100Mbytesの「in」という入力ファイルと、同じく大きさ100Mbytesの「out」という出力ファイルを用意する。それぞれのファイルにはランダムデータを書き込むようにする。これは、inとoutの中身が違うものになるようにして、ファイルコピー後に内容が同じものになることを確認するためだ。ファイルの中身の違いはハッシュ値を調べれば分かる。中身がまったく同じなら、ハッシュ値も同じものになる。
入力ファイルであるinは次のようにコマンドを実行して用意する。
dd if=/dev/random of=in bs=512 count=204800
出力ファイルoutも同じようにコマンドを実行して用意する。
dd if=/dev/random of=out bs=512 count=204800
先に説明したように、今回はtruss(1)コマンドでシステムコール呼び出し回数を調べるだけでなく、time(1)コマンドを利用して物理メモリの使用量、ページフォールトの発生回数、コンテキストスイッチの発生回数なども調べる。truss(1)コマンドは以下のように実行すれば良い。
truss -c ./プログラム名
今回使用するtime(1)コマンドは、シェル組み込み関数のtimeではなく、FreeBSD/PC-BSDのユーザーランドにインストールされている/usr/bin/timeであることに注意してほしい。今回調べるデータを得るには、以下のように「-lph」オプションを付けて実行すれば良い。
/usr/bin/time -lph ./プログラム名
なお、/usr/bin/timeは同じ結果を得るためのオプションがOSによって異なる。例えばMac OS Xなら「-lp」、Ubuntuなら「-pv」となる。
テストをまとめて実行するために、Makefileを作ると次のようになる。
CC=clang all: test1 test2 test3 test4 test5 test6 test7 test8 build: copy-fgetc-fputc copy-mmap copy-mmap2 copy-fgetc-fputc: ${CC} -o copy-fgetc-fputc copy-fgetc-fputc.c copy-mmap: ${CC} -o copy-mmap copy-mmap.c copy-mmap2: ${CC} -o copy-mmap2 copy-mmap2.c test1: copy-fgetc-fputc dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out truss -c ./copy-fgetc-fputc test2: copy-fgetc-fputc dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out /usr/bin/time -lph ./copy-fgetc-fputc test3: copy-mmap dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out truss -c ./copy-mmap test4: copy-mmap dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out /usr/bin/time -lph ./copy-mmap test5: copy-mmap2 dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out truss -c ./copy-mmap2 test6: copy-mmap2 dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out /usr/bin/time -lph ./copy-mmap2 test7: dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out truss -c cp in out test8: dd if=/dev/random of=in bs=512 count=204800 dd if=/dev/random of=out bs=512 count=204800 md5 in out /usr/bin/time -lph cp in out clean: rm -f copy-fgetc-fputc copy-mmap copy-mmap2 in out
2ページ目では、truss(1)とtime(1)で調べた結果から、それぞれのサンプルプログラムの特性を分析していく。
Copyright © ITmedia, Inc. All Rights Reserved.