厳密にはバッチコマンドで計測できる時間はプログラムの実行時間ではない。ここで 、 プログラムが実行されるしくみを解説する。
プログラムはソースファイル(.CPP)として作成され、コンパイルによってオブジェ クトファイル(.OBJ)に一旦変換され、最終的にライブラリとリンクされ実行可能ファ イル(.EXE)に変換される。VSではコンパイルとリンクの過程を合わせてビルドという。
この実行可能ファイルを実際に実行するには、実行可能ファイルをメモリに読み込み
(ロード)、実行開始番地へ分岐する必要がある。実行開始番地はmain関数と等しい。
したがって、main関数を実行する前に実行可能ファイルをロードする時間を要する。こ のロード時間を起動時間と呼ぶ。また、実行開始後から終了するまでに要する時間を純粋 な実行時間と呼ぶ。両者の合計時間を応答時間と呼ぶ。
プログラムの応答時間の測定
実験1で測定した時間には起動時間が含まれている。すなわち応答時間である。 ここ で、以下のようなバッチファイルを考える。
@echo of second.exe
FOR /L %%I IN (1,1,10000) DO A.exe >NUL second.exe
PAUSE
ここで、second.exeは秒単位の時間を表示するプログラムである(先週の実験書参 照)。
このバッチファイルの応答時間は以下のような時間の和である。
バッチファイルの応答時間=second.exeの応答時間×2+A.exeの応答時間×N ここで、NはA.exeの繰り返し回数である。ここではN=10000である。両辺をNで 割り、Nを十分大きくすればsecond.exeの応答時間は無視できる。
A.exeの応答時間≒バッチファイルの応答時間/N
ここで、Nが十分大きくなければseond.exeの応答時間を無視できないことに注意す る。正確な測定を望むならseond.exeの応答時間を測定しておくとよい。A.exeの代わ
りにsecond.exeを実行すればsecond.exeの応答時間を求めることができる。
この方法では、A.exeの起動時間がわからなければ正確な実行時間はわからない。
実験2 second.exeの応答時間の測定
second.exeの応答時間を測定せよ。なお、有効数字は2桁とする。得られた結果から先
週の実験1の結果についても考察せよ。
プログラムの一部の実行時間の測定
プログラム全体の実行時間(すなわち応答時間)には、プログラム以外の処理が含ま れる。例えば、プログラムをメモリにロードする時間などである。そのためプログラム 全体を測定すると誤差が大きくなる。また、プログラムの一部の実行時間を測定するこ ともできない。プログラムの一部の実行時間を正確に測定することができればプログラ ム全体の実行時間も正確に測定できる。ここでは、プログラムの一部の実行時間を正確に 測定する方法について解説する。
time関数はプログラム中の任意の場所で使用できる。そこで、以下のようなプログラ ムを考える。
#include <stdio.h>
#include <time.h>
void main() {
time_t t0,t1,lap;
time(&t0);
// 対象コード time(&t1);
lap = t1 - t0;
printf("%d\n", lap);
}
このプログラムはt0からt1までの時間を測定し、経過時間を秒単位で表示する。コメ ント部分に任意のコードを埋め込むことでそのコードの実行時間を測定することができ る。しかし、このままでは1秒未満で終わるコードを測定することはできない。
プログラムの一部は全体より短いため精度の高い測定を必要とする。測定精度を高め るには繰り返しが必要である。繰り返しを入れたプログラムは以下のようになる。
1:
2:
3:
4:
5:
6:
7:
#include <stdio.h>
#include <time.h>
#define N 10000 // 繰り返し回数 void main() {
time_t t0,t1;
: 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 :
time(&t0);
for(i=0; i<=N-1; i=i+1) { // 対象コード }
time(&t1);
lap = t1 - t0;
if (lap > 100) printf("%f\n", lap/N);
}
このプログラムは繰り返し回数Nが十分大きければ1秒未満のコードの時間も測定で きる。なお、17行目の100は2桁の精度で測定するための定数である。1桁の場合は 10でよい。精度が不十分なら何も表示しない。
ここで、t0とt1の間にfor文があることに注意する。プログラム断片(プログラムの 一部)が非常に小さい場合、繰り返し自身の時間を無視できない。例えば、足し算1命令 の実行時間を測定するとき、精度を上げるための繰り返しの方が観測対象である足し算 より多くの時間を費やす。そのため大きな誤差が生じる。実行時間を正確に測定するに は、断片を除いたプログラムと比較する必要がある。
比較を行うプログラムを以下に示す。
1:
2:
3:
4:
5:
6:
#include <stdio.h>
#include <time.h>
#define N 10000 // 繰り返し回数 void main() {
7:
8:
9:
10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 :
time_t t0,t1;
float lap0,lap1;
int i;
time(&t0);
for(i=0; i<=N-1; i++){
int i, t0, lap0;
}
time(&t1);
lap0 = t1 - t0;
time(&t0);
for(i=0; i<=N-1; i++){
int i, t0, lap0;
// 計測対象コード }
time(&t1);
lap1 = t1 - t0;
if (lap1-lap0 > 100) printf("%f\n", (lap1-lap0)/N);
}
いる。比較のために13行目にも20行目と同じ宣言を行う1。
1:
2:
3:
4:
5:
6:
7:
8:
9:
10 :
#include <stdio.h>
#include <time.h>
void main() { int c=0, i;
for(i=1; i<=1000000; i=i+1) { c = c + 1;
} }
図 1 簡単なプログラム
いま、図 1のような簡単なプログラムの実行時間を測定することを考える。これは1
から1000000まで数える単純なプログラムである。1回の実行時間は1秒に満たないた
め測定は困難である。このような場合、プログラムは以下のようになる。
1:
2:
3:
4:
5:
6:
7:
8:
9:
10 : 11 :
#include <stdio.h>
#include <time.h>
#define N 10000 // 繰り返し回数
void main() { time_t t0,t1 float lap0,lap1;
int i;
time(&t0);
for(i=0; i<=N-1; i++){
int i, t0, lap0;
1 実際には、これは気休めに過ぎない。コンパイラが変数を削除してしまうこともある。
12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29
}
time(&t1);
lap0 = t1 - t0;
time(&t0);
for(i=0; i<=N-1; i++){
int i, t0, lap0;
// 計測対象コード
int c=0, k; // 計測対象コードの変数 for(k=1; k<=1000000; k=k+1) {
c = c + 1;
} }
time(&t1);
lap1 = t1 - t0;
if (lap1-lap0 > 100) printf("%f\n", (lap1-lap0)/N);
}
対象となるコードは22~25行である。繰り返し回数はマシンと求めたい有効桁数に依 存する。結果は秒数で表示される。
演習1 プログラムの一部の実行時間
図 1のプログラムの一部(for文全体)の実行時間を有効数字2桁まで測定しなさい。
ヒント 測定対象のコードを比較プログラムの対象コードと置き換える。必要な有効数 字が得られるように繰り返し回数を調整する。
プログラムの起動時間
冒頭で述べたように、プログラムの応答時間は起動時間と純粋な実行時間に分けられる。
言い換えれば、起動時間は応答時間から実行時間を引いた時間である。ここで、応答時間 、 実行時間がともに誤差を含む値であるとき、加減算で精度がどのように変わるか知る必 要がある。
例えば、有効数字2桁の値1.2×10-2と3.4×10-3があるとき、加減算を行うには、ま ず両者の小数点の位置をそろえる。
1.20 ×10-2 0.34 ×10-2
次に加減算(ここでは減算)を行う。結果は以下のようになる。
0.86 ×10-2
一見、8.6×10-3となり、有効数字が2桁あるように見える。しかし、この計算には誤 りがある。なぜならば1.2×10-2≠1.20×10-2だからである。1.2×10-2が切捨ての結果な らば1.20×10-2~1.29×10-2である。正確に言えば[1.2×10-2, 1.3×10-2)の中に真の値が ある2。0.34×10-2も同様に[0.34×10-2, 0.35×10-2)と表せる。そこで、それぞれの下限 と上限に対して加減算を行う。
1.2×10-2-0.34×10-2=0.86×10-2 1.2×10-2-0.35×10-2=0.85×10-2 1.3×10-2-0.34×10-2=0.96×10-2 1.3×10-2-0.35×10-2=0.95×10-2
式中に範囲外の値がある場合は結果も範囲外になる。よって、(0.85×10-2, 0.96×10-
2)となる。この範囲を有効数字で表すために最も適切な数値を選択すると0.9×10-2とな
る。しかし、この値0.9×10-2が示す真の値の範囲が0.90×10-2~0.99×10-2であるわけ ではない。次に0.9×10-2を用いて加減算するとき誤差の範囲を知らないと真の値を見失
2 [a,b)はa以上b未満を表す。角括弧の端は含まれ、丸括弧の端は含まれない。
ってしまう。
このように有効数字を用いた場合、真の値を失うような加減算を行ってはいけない。
常に有効数字が表す範囲に真の値があるようにしなければならない。上の例ではどのよ うにすれば真の値を表すことができるのだろうか?
ここで、1.2×10-2の有効数字を増やし1.23×10-2だとする。すると、真の値の範囲は [1.23×10-2, 1.24×10-2)と な る 。1.23×10-2-0.34×10-2の 結 果 の範 囲は(0.88×10-2,
0.90×10-2)となる。この場合、有効数字として0.8×10-2を選択すれば、その範囲は
[0.8×10-2, 0.9×10-2)であるため十分カバーできる。ただし、誤差の範囲は広がる。
実験3 プログラムの一部の起動時間
サンプルプログラム(付録A)の一部(外側のfor文全体)の起動時間を有効数字2桁 まで測定しなさい。
ヒント 測定対象のコードを比較プログラムの対象コードと置き換える。必要な有効数 字が得られるように繰り返し回数を調整する。応答時間と実行時間を両方測定し、差 を求める。
課題
コマンドプロンプトのバッチファイルで使用できるコマンドについて調べ、測定を容 易にする方法を考察しなさい。
先週の実験1と今週の実験2,3をレポートR2にしなさい。
付録A サンプルプログラム
10000以下の素数の数を数えるプログラム(L07X01.cpp)
#include <stdio.h>
void main() {
int n, k, f, c = 0;
for(n=2; n<=10000; n=n+1) { f = 1;
for(k=2; k*k<=n; k=k+1) { if (n % k == 0) {
f = 0;
break;
}
} }
printf("%d\n", c);
}