はじめに
以前書いた記事『最速のFizzBuzzを目指してみた』で性能評価を忘れていた。馬鹿か俺は。
そんな訳で抜けていたプログラムの性能評価を行いました。
評価方法
前回作成した FizzBuzz のプログラムを
$ time -p ./fizzbuzz >/dev/null
real 0.00
user 0.00
sys 0.00
$
等と実行しても time コマンドで測定するにはプログラムの実行時間が短すぎるので役に立ちません。少し工夫して
$ time -p (for i in `seq 1 10000`; do ./fizzbuzz; done) >/dev/null
real 6.76
user 4.55
sys 4.41
複数回実行して実行時間を実行回数で割るという方法が考えられますが、シェルによる繰り返しはプログラムの実行時間と較べ負荷が大きそうなのでこの方法は採りません。今回は同一のプログラムを繰り返し実行する短いプログラムをいいかげんにでっちあげてそれを使用することとしました。
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <spawn.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
int opt;
int n = 1;
bool f = false;
int fd = -1;
opterr = 0;
while ((opt = getopt(argc, argv, "hfn:")) != -1) {
switch (opt) {
case 'h':
default:
fprintf(stderr, "Usage: repeat [options]... command\n");
exit(EXIT_FAILURE);
break;
case 'f':
f = true;
break;
case 'n':
n = atoi(optarg);
break;
}
}
if (f) {
fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
}
for (int i = 1; i <= n; i++) {
if (f && fd >= 0) {
while (write(fd, "1", 1) == -1 && errno == EINTR) {
;
}
}
pid_t pid;
posix_spawn(&pid, argv[optind], NULL, NULL, &argv[optind], NULL);
int status;
wait(&status);
if (status) {
fprintf(stderr, "pid %d: %d\n", pid, status);
exit(status);
}
}
if (f && fd >= 0) {
close(fd);
}
}
これを
$ gcc -Wall -Wextra -O2 repeat.c -o repeat
としてコンパイルし、
$ time -p ./repeat -n 10000 ./fizzbuzz >/dev/null
real 1.84
user 1.44
sys 0.48
$
として使用します。コマンドラインオプション `-n 値' でプログラムの繰り返し実行する回数を指定し、time コマンドによって出力された数字を実行回数で割り、1回当たりの実行時間ということとします。
プログラムは一度ディスクから読み込まれるとその内容はページキャッシュに登録され、繰り返し実行の際にはディスクを読みに行かなくなります。プログラムのディスクからの読み込み負荷も評価したいので、コマンドラインオプション `-f' を指定することでページキャッシュをクリアした状態からプログラムを実行する機能を設けています。ページキャッシュのクリアは /proc/sys/vm/drop_caches に 1 を書き込むことで行っており、管理者権限が必要なので sudo を併用する等必要があります。
$ sudo time -p ./repeat -n 10000 -f ./fizzbuzz >/dev/null
real 85.90
user 3.26
sys 78.85
$
これも先の例と同様に出力された数字を繰り返し回数で割ることでプログラムの読み込み時間を含んだ実行時間 とします。
測定結果
キャッシュクリアなし(※ 数値は 10000回実行した時間(秒))
C言語ダイナミックリンク版 | C言語スタティックリンク版 | アセンブラ版 | |
---|---|---|---|
real | 4.55 | 2.99 | 1.82 |
user | 3.65 | 2.37 | 1.43 |
sys | 0.91 | 0.69 | 0.47 |
キャッシュクリアあり(※ 同上)
C言語ダイナミックリンク版 | C言語スタティックリンク版 | アセンブラ版 | |
---|---|---|---|
real | 121.74 | 142.39 | 85.90 |
user | 8.15 | 9.46 | 3.26 |
sys | 93.45 | 95.96 | 78.85 |
実行ファイルサイズ(※ 単位はバイト)
C言語ダイナミックリンク版 | C言語スタティックリンク版 | アセンブラ版 |
---|---|---|
6112 | 714736 | 808 |
評価
- キャッシュクリアなし/ありの両方でアセンブラ版が最速という結果となった(とりあえずは一安心)
- キャッシュクリアなしの条件ではディスク読み込みが初回以外発生しないので実行ファイルサイズは実行時間にほぼ影響がない(筈)。発生している差は以下の 2点によるものと思われる
- Cで書かれたプログラムのスタートアップに要する時間(C言語スタティックリンク版, C言語ダイナックリンク版)
- ダイナミックリンクに要する時間(C言語ダイナミックリンク版)
- キャッシュクリアなしの条件では user+sys≒real が凡そ成り立つがキャッシュクリアありの条件では user+sys と real の値に違いがあり、その差はディスク読み込み待ちに要する時間と思われる。そのため、キャッシュクリアありの条件では実行ファイルサイズが大きい程 real の数字への影響も大きいと考えられる
おわりに
おわりです。