関数の実行にかかる時間をどのように測定しますか?
質問
関数の実行にかかる時間をどのように測定できますか?
これは比較的短い関数であり、実行時間はおそらくミリ秒の範囲になります。
この特定の質問は、C または C++ でプログラムされた組み込みシステムに関するものです。
解決
組み込みシステムでこれを行う最善の方法は、関数に入るときに外部ハードウェア ピンを設定し、関数から出るときにそれをクリアすることです。これは、結果が大きく偏らないように、少しの組み立て命令を使用して実行することが望ましいです。
編集:利点の 1 つは、実際のアプリケーションで実行でき、特別なテスト コードが必要ないことです。このような外部デバッグ ピンは、すべての組み込みシステムで標準的な方法です (そうあるべきです!)。
他のヒント
考えられる解決策は 3 つあります。
ハードウェアソリューション:
プロセッサ上の空き出力ピンを使用し、オシロスコープまたはロジック アナライザをピンに接続します。測定する関数を呼び出す直前にピンを Low 状態に初期化し、ピンを High 状態にアサートし、関数から戻った直後にピンをディアサートします。
*io_pin = 1;
myfunc();
*io_pin = 0;
本の虫の解決策:
関数がかなり小さく、逆アセンブルされたコードを管理できる場合は、プロセッサ アーキテクチャのデータブックを開き、プロセッサがすべての命令を実行するのにかかるサイクルをカウントできます。これにより、必要なサイクル数が得られます。
時間 = サイクル数 * プロセッサ クロック レート / 命令ごとのクロック ティック数
これは、小さな関数やアセンブラで書かれたコード (PIC マイクロコントローラーなど) の場合に実行しやすいです。
タイムスタンプカウンターソリューション:
一部のプロセッサには、高速 (プロセッサ クロックの数刻みごと) で増加するタイムスタンプ カウンタが備わっています。関数の前後のタイムスタンプを読み取るだけです。これにより経過時間がわかりますが、カウンターのロールオーバーに対処する必要がある場合があることに注意してください。
大量の呼び出しを含むループで呼び出し、呼び出し数で割って平均時間を取得します。
それで:
// begin timing
for (int i = 0; i < 10000; i++) {
invokeFunction();
}
// end time
// divide by 10000 to get actual time.
Linux を使用している場合は、コマンド ラインに次のように入力してプログラムの実行時間を計測できます。
time [funtion_name]
main() 内の関数のみを実行する場合 (C++ を想定)、アプリの残りの時間は無視できるはずです。
関数呼び出しを何百万回も繰り返しますが、ループのオーバーヘッドを割り引くために次の方法も採用しています。
start = getTicks();
repeat n times {
myFunction();
myFunction();
}
lap = getTicks();
repeat n times {
myFunction();
}
finish = getTicks();
// overhead + function + function
elapsed1 = lap - start;
// overhead + function
elapsed2 = finish - lap;
// overhead + function + function - overhead - function = function
ntimes = elapsed1 - elapsed2;
once = ntimes / n; // Average time it took for one function call, sans loop overhead
function() を最初のループで 2 回、2 番目のループで 1 回呼び出す代わりに、最初のループで 1 回だけ呼び出し、まったく呼び出さないようにすることもできます。ただし、空のループはコンパイラによって最適化され、マイナスのタイミング結果が得られる可能性があります:)
start_time = timer
function()
exec_time = timer - start_time
Windows XP/NT Embedded または Windows CE/Mobile
QueryPerformanceCounter() を使用して、関数の前後で VERY FAST カウンターの値を取得します。次に、これらの 64 ビット値を減算して、デルタ「ティック」を取得します。QueryPerformanceCounterFrequency() を使用すると、「デルタ ティック」を実際の時間単位に変換できます。これらの WIN32 呼び出しについては、MSDN ドキュメントを参照してください。
その他の組み込みシステム
オペレーティング システムを使用しない場合、または基本 OS のみを使用する場合は、次のことを行う必要があります。
- 内部 CPU タイマーの 1 つをプログラムして、自由に実行およびカウントできるようにします。
- タイマーがオーバーフローしたときに割り込みを生成するように設定し、この割り込みルーチンで「キャリー」変数をインクリメントします (これにより、選択したタイマーの分解能よりも長い時間を実際に測定できるようになります)。
- 関数の前に、「キャリー」値と、設定したカウントタイマーの実行中のティックを保持する CPU レジスタの値の両方を保存します。
- 関数の後も同じです
- それらを減算してデルタカウンターティックを取得します。
- そこからは、外部クロックとタイマーの設定時に設定した逆乗算を考慮して、CPU/ハードウェア上でティックがどのくらいの時間を意味するかを知るだけです。その「ティック長」に、先ほど取得した「デルタティック」を掛けます。
非常に重要 これらのタイマー値 (キャリーとレジスタ値) を取得する前に割り込みを無効にし、取得後に割り込みを復元することを忘れないでください。そうしないと、間違った値を保存する危険があります。
ノート
- 割り込みを無効にし、2 つの整数値を保存し、割り込みを再度有効にするのに数個のアセンブリ命令を実行するだけなので、これは非常に高速です。実際の減算とリアルタイム単位への変換は、時間測定ゾーンの外側、つまり関数の後に発生します。
- そのコードを関数に入れてそのコードを再利用したいと思うかもしれませんが、関数呼び出しとすべてのレジスタとパラメータをスタックにプッシュし、再度ポップするため、処理が少し遅くなる可能性があります。組み込みシステムでは、これは重要な場合があります。C でマクロを使用するよりも、代わりに MACROS を使用するか、関連するレジスタのみを保存/復元する独自のアセンブリ ルーチンを作成する方が良い場合があります。
組み込みプラットフォームと、どのようなタイミングを求めているかによって異なります。組み込み Linux の場合は、いくつかの方法で実現できます。関数によって使用される CPU 時間を測定したい場合は、次の手順を実行できます。
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#define SEC_TO_NSEC(s) ((s) * 1000 * 1000 * 1000)
int work_function(int c) {
// do some work here
int i, j;
int foo = 0;
for (i = 0; i < 1000; i++) {
for (j = 0; j < 1000; j++) {
for ^= i + j;
}
}
}
int main(int argc, char *argv[]) {
struct timespec pre;
struct timespec post;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &pre);
work_function(0);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &post);
printf("time %d\n",
(SEC_TO_NSEC(post.tv_sec) + post.tv_nsec) -
(SEC_TO_NSEC(pre.tv_sec) + pre.tv_nsec));
return 0;
}
これをリアルタイム ライブラリにリンクする必要があります。次のコードを使用してコードをコンパイルするだけです。
gcc -o test test.c -lrt
のマニュアルページもお読みください。 clock_gettime
SMP ベースのシステムでこのコードを実行すると、テストが無効になる可能性があるいくつかの問題があります。次のようなものを使用できます sched_setaffinity()
またはコマンドライン cpuset
コードを 1 つのコアのみに強制的に適用します。
ユーザー時間とシステム時間を測定したい場合は、 times(NULL)
これはすぐに何かを返します。または、パラメータを変更することもできます clock_gettime()
から CLOCK_THREAD_CPUTIME_ID
に CLOCK_MONOTONIC
...ただし、ラップアラウンドには注意してください CLOCK_MONOTONIC
.
他のプラットフォームの場合は、自己責任で行ってください。
ドリュー
私は常に割り込み駆動のティッカー ルーチンを実装しています。これにより、起動からのミリ秒数をカウントするカウンターが更新されます。このカウンターは、GetTickCount() 関数を使用してアクセスされます。
例:
#define TICK_INTERVAL 1 // milliseconds between ticker interrupts
static unsigned long tickCounter;
interrupt ticker (void)
{
tickCounter += TICK_INTERVAL;
...
}
unsigned in GetTickCount(void)
{
return tickCounter;
}
コードでは、次のようにコードの時間を計測します。
int function(void)
{
unsigned long time = GetTickCount();
do something ...
printf("Time is %ld", GetTickCount() - ticks);
}
OS X ターミナル (おそらく Unix も) では、「time」を使用します。
time python function.py
コードが .Net の場合は、DateTime.Now ではなくストップウォッチ クラス (.net 2.0 以降) を使用します。DateTime.Now は十分に正確に更新されていないため、おかしな結果が得られます
ミリ秒未満の解像度を探している場合は、次のタイミング方法のいずれかを試してください。これらはすべて、少なくとも数十または数百マイクロ秒で解像度を取得します。
組み込み Linux の場合は、Linux タイマーを確認してください。
http://linux.die.net/man/3/ Clock_gettime
組み込み Java については、nanoTime() を見てください。ただし、これが組み込み版にあるかどうかはわかりません。
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html#nanoTime()
ハードウェア カウンターにアクセスしたい場合は、PAPI を試してください。
それ以外の場合は、いつでもアセンブラーを使用できます。これについてサポートが必要な場合は、アーキテクチャの PAPI ソースを参照してください。