PSLブログ

ヨシナシゴトヲツヅリマス

【C言語】1~100万までの数字をランダマイズする(2)

昨日の続きです。

blog.psl.ne.jp

実行時間の計測をしたい

この処理にどのくらいの時間がかかるかを可視化したいが、time()だと正数秒単位でしか計測できず、今回の1~100万の数字のランダマイズでは1秒未満のため0秒となってしまう。clock()では実時間が測れない。で、探したところ、timespec_get()を見つけた。time.hをインクルードすれば使える。厳密にいうと、プログラムが起動してから終了するまでを測りたいが、コード内に書くので、メインの処理前と処理後にそれぞれ計測して、差分を出すことにした。

struct timespec st_start, st_end;
float diff;
long sec, nsec;
timespec_get(&st_start, TIME_UTC);
// メイン処理
timespec_get(&st_end, TIME_UTC);

// 経過時間を算出
sec = end.tv_sec - start.tv_sec - (start.tv_nsec > end.tv_nsec ? 1 : 0);
nsec = end.tv_nsec - start.tv_nsec + (start.tv_nsec > end.tv_nsec ? 1000000000 : 0);
diff = sec + nsec / 1000000000.0;
printf("経過時間は %.4f 秒でした。\n", diff);

timespec_getが整数部分とマイクロ秒(100万分の1秒)部分とに分かれて受け取るため、まどろっこしい計算になっている。

この時間計測の処理と、配列のランダマイズの処理をそれぞれ関数にしたコードは以下の通り。

shuffle.cpp

#include <time.h>
#include <stdlib.h>
#include <stdio.h>

#define NUMBER 1000000

int main(void) {

	int i;
	int *seekno;
	FILE *fp;
	struct timespec st_start, st_end;
	float diff;
	float get_diff (timespec end, timespec start);
	void shuffle(int array[], int size);

	timespec_get(&st_start, TIME_UTC);

	printf("要素数 %d 個の配列を生成してランダマイズします。\n", NUMBER);

	seekno = (int *)malloc(NUMBER * sizeof(int));

	for (i=0; i<NUMBER; i++) {
		seekno[i] = i;
	}
	srand((int)st_start.tv_sec);
	shuffle(seekno, NUMBER);

	// シャッフル後の配列の冒頭10件を表示してみる
	for (i = 0; i < 10; i++) {
		printf("%d,", seekno[i]);
	}
	printf("\n\n");

	// シャッフル後の配列を書き出す
	fp = fopen("shuffle.txt", "w");
	for (i=0; i<NUMBER; i++){
		fprintf(fp, "%d\n", seekno[i]);
	}
	fclose(fp);

	timespec_get(&st_end, TIME_UTC);

	printf("要素数 %d 個の配列を生成しました。\n", NUMBER);
	printf("経過時間は %.4f 秒でした。\n", get_diff(st_end, st_start));

	return 0;

}

float get_diff (timespec end, timespec start) {
	long sec = end.tv_sec - start.tv_sec - (start.tv_nsec > end.tv_nsec ? 1 : 0);
	long nsec = end.tv_nsec - start.tv_nsec + (start.tv_nsec > end.tv_nsec ? 1000000000 : 0);
//	printf("get_diff: %9ld, %9ld\n", sec, nsec);
	return sec + nsec / 1000000000.0;
}

void shuffle(int array[], int size) {
	for(int i = 0; i < size; i++) {
		int j = (int)(rand() * 1000000.0 / RAND_MAX);
		int tmp = array[i];
		array[i] = array[j];
		array[j] = tmp;
	}
}

Cの文法に全く疎いままだが、shuffle()の第1引数は配列の参照渡しのようになっていらしい。戻り値を受け取らなくても、seeknoが更新されている。

これを実行すると、

C:\***\***\ctest>shuffle
素数 1000000 個の配列を生成してランダマイズします。 995244,347819,497573,612292,464339,154942,336466,550950,573412,179662, 要素数 1000000 個の配列を生成しました。
経過時間は 0.3006 秒でした。

となる。CPUの能力によるが、私のPCでは、0.3~0.4秒くらいとなった。

おまけ:Perlで書いた場合

これ、Perlで書いたら楽だなあと思い、なるべくCのコードに似せるようにして書いてみた。ただし、List::Util::shuffleとTime::HiResを使った。

use strict;
use List::Util;
use Time::HiRes qw(gettimeofday tv_interval);
my $NUMBER = 1000000;

my $start = [gettimeofday()];

printf("要素数 %d 個の配列を生成してランダマイズします。\n", $NUMBER);

my @seekno = List::Util::shuffle (0..$NUMBER-1);

### シャッフル後の配列の冒頭10件を表示してみる
print join(",", @seekno[0..9]), "\n\n";

open(my $fh, ">", "shuffle_pl.txt");
print $fh "$_\n" for @seekno;
close($fh);

printf("要素数 %d 個の配列を生成しました。\n", $NUMBER);
printf("経過時間は %.4f 秒でした。\n", tv_interval($start));

うーむ短い。中間変数をかなり省略できるのがよい。このコードを実行すると、0.4~0.6秒くらいとなった。実行のたびに数値に揺れがあるので、それぞれ100回実行するように書き換えて比べたところ、

C…27.7651秒

Perl…41.8353秒

となった。やはりCの方が速いが、開発効率まで考えるとPerlのメリットも十分あると感じた。ただ今回は速度最優先+クライアントの指定のため、Cを使うしかない。