独自の非常に単純なプロファイラーを作成することは、それほど難しくありません。main() に挿入します。
int main()
{
profileCpuUsage(1); // start timer #1
well_written_function();
profileCpuUsage(2); // stop timer #1, and start timer #2
badly_written_function();
profileCpuUsage(-1); // print stats for timers #1 and #2
return 0;
}
どこ:
#define NUMBER(a) ((int)(sizeof(a) / sizeof(a)[0]))
void profileCpuUsage(int slice)
{
static struct {
int iterations;
double elapsedTime;
} slices[30]; // 0 is a don't care slice
if (slice < 0) { // -1 = print
if (slices[0].iterations)
for (slice = 1; slice < NUMBER(slices); slice++)
printf("Slice %2d Iterations %7d Seconds %7.3f\n", slice,
slices[slice].iterations, slices[slice].elapsedTime);
}
else {
static int i; // = previous slice
static double t; // = previous t1
const double t1 = realElapsedTime(); // see below for definition
assert (slice < NUMBER(slices));
slices[i].iterations += 1;
slices[i].elapsedTime += t1 - t; // i = 0 first time through
i = slice;
t = t1;
}
}
確かに、この profileCpuUsage() を使用した簡単な例では、あまりメリットがありません。また、適切な場所で profileCpuUsage() を呼び出してコードを手動で計測する必要があるという欠点もあります。
ただし、次のような利点があります。
- プロシージャだけでなく、任意のコード フラグメントの時間を計ることができます。
- バイナリ検索を実行してコードのホットスポットを見つけたり削除したりするため、追加や削除がすばやくできます。
- 関心のあるコードのみに焦点を当てています。
- ポータブル!
- 接吻
移植性のないトリッキーなことの 1 つは、関数 realElapsedTime() を定義して、有効な時間を取得するのに十分な粒度を提供することです。これは通常、私にとってはうまくいきます(CYGWINでWindows APIを使用):
#include <windows.h>
double realElapsedTime(void) // <-- granularity about 50 microsec on test machines
{
static LARGE_INTEGER freq, start;
LARGE_INTEGER count;
if (!QueryPerformanceCounter(&count))
assert(0 && "QueryPerformanceCounter");
if (!freq.QuadPart) { // one time initialization
if (!QueryPerformanceFrequency(&freq))
assert(0 && "QueryPerformanceFrequency");
start = count;
}
return (double)(count.QuadPart - start.QuadPart) / freq.QuadPart;
}
ストレートな Unix の場合、次の共通点があります。
double realElapsedTime(void) // returns 0 first time called
{
static struct timeval t0;
struct timeval tv;
gettimeofday(&tv, 0);
if (!t0.tv_sec)
t0 = tv;
return tv.tv_sec - t0.tv_sec + (tv.tv_usec - t0.tv_usec) / 1000000.;
}
realElapsedTime() は、プロセス時間ではなく実時間で表示されます。これは通常、私が求めているものです。
RDTSC を使用してより細かい粒度を実現するための移植性の低い方法もあります。たとえばhttp://en.wikipedia.org/wiki/Time_Stamp_Counterとそのリンクを参照してください。ただし、これらは試していません。
編集: ravenspointの非常に素晴らしい答えは、私のものとあまり似ていないようです。そして彼の答えは、私がしばしば不満を感じていた醜い数字ではなく、わかりやすい文字列を使用しています。しかし、これは約 12 行追加するだけで修正できます (ただし、これにより行数はほぼ2 倍になります!)。
malloc() の使用を避けたいことに注意してください。また、私は strcmp() についても少し懐疑的です。したがって、スライスの数が増えることはありません。また、ハッシュの衝突は、解決されるのではなく、単純にフラグが立てられます。人間のプロファイラーは、手動でスライスの数を 30 から増やすか、説明を変更することで、これを修正できます。未テスト
static unsigned gethash(const char *str) // "djb2", for example
{
unsigned c, hash = 5381;
while ((c = *str++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
void profileCpuUsage(const char *description)
{
static struct {
int iterations;
double elapsedTime;
char description[20]; // added!
} slices[30];
if (!description) {
// print stats, but using description, mostly unchanged...
}
else {
const int slice = gethash(description) % NUMBER(slices);
if (!slices[slice].description[0]) { // if new slice
assert(strlen(description) < sizeof slices[slice].description);
strcpy(slices[slice].description, description);
}
else if (!!strcmp(slices[slice].description, description)) {
strcpy(slices[slice].description, "!!hash conflict!!");
}
// remainder unchanged...
}
}
もう 1 つのポイントは、通常、リリース バージョンではこのプロファイリングを無効にすることです。これはravenspointの答えにも当てはまります。これは、邪悪なマクロを使用してそれを定義するトリックによって実行できます。
#define profileCpuUsage(foo) // = nothing
これが行われた場合、もちろん、定義に括弧を追加して、無効化マクロを無効にする必要があります。
void (profileCpuUsage)(const char *description)...