滚动您自己的非常简单的分析器并不难。插入 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() 来手动检测代码。
但优点包括:
- 您可以对任何代码片段进行计时,而不仅仅是程序。
- 可以快速添加和删除,因为您执行二进制搜索以查找和/或删除代码热点。
- 它只关注您感兴趣的代码。
- 便携的!
- 吻
一件棘手的不可移植的事情是定义函数 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及其链接,但我没有尝试过这些。
编辑: ravenpoint 的非常好的答案似乎与我的不太相似。他的回答使用了很好的描述性字符串,而不仅仅是丑陋的数字,我经常对此感到沮丧。但这可以通过大约十几个额外的行来解决(但这几乎是行数的 两倍!)。
请注意,我们要避免使用 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...
}
}
另一点是,通常您会希望为发布版本禁用此分析;这也适用于 ravenspoint 的回答。这可以通过使用 evil 宏来定义它的技巧来完成:
#define profileCpuUsage(foo) // = nothing
如果这样做了,您当然需要在定义中添加括号以禁用禁用宏:
void (profileCpuUsage)(const char *description)...