LoginSignup
3
3

More than 5 years have passed since last update.

「最速のFizzBuzzを目指してみた」とか抜かした癖に性能評価を忘れていた

Posted at

はじめに

以前書いた記事『最速の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

複数回実行して実行時間を実行回数で割るという方法が考えられますが、シェルによる繰り返しはプログラムの実行時間と較べ負荷が大きそうなのでこの方法は採りません。今回は同一のプログラムを繰り返し実行する短いプログラムをいいかげんにでっちあげてそれを使用することとしました。

repeat.c
#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 の数字への影響も大きいと考えられる

おわりに

おわりです。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3