使用系统单调时钟分析相对 C 代码执行时间
Relative C code execution time analysis using system monotonic clock
是否有一种简单但可靠的方法来衡量 C 程序中两种算法实现之间的相对性能差异。更具体地说,我想比较实施 A 与 B 的性能?我正在考虑这样的方案:
在单元测试程序中:
- 启动计时器
- 调用函数
- 停止计时器
- 获取开始停止时间之间的差异
- 运行 上面的方案对于一对函数A和B,然后得到执行时间的百分比差异来确定哪个更快。
在做一些研究后,我遇到了这个关于使用 Monotonic clock on OSX in C, which apparently can give me at least nanosecond precision. To be clear, I understand that precise, controlled measurements are hard to perform, like what's discussed in 的问题,我认为在这种情况下应该无关紧要,因为我只想要一个相对测量值。
综合考虑,对于我想要执行的那种分析,这是一种充分有效的方法吗?有没有我可能遗漏的细节或注意事项?
我对您概述的时序方案所做的主要修改是通过将函数指针传递给骨架代码来确保两个函数使用相同的时序代码——假设它们确实具有相同的接口。
例如,我有一些代码可以对一些函数进行计时,这些函数可以验证给定数字是否为素数。控制函数为:
static void test_primality_tester(const char *tag, int seed, int (*prime)(unsigned), int count)
{
srand(seed);
Clock clk;
int nprimes = 0;
clk_init(&clk);
clk_start(&clk);
for (int i = 0; i < count; i++)
{
if (prime(rand()))
nprimes++;
}
clk_stop(&clk);
char buffer[32];
printf("%9s: %d primes found (out of %d) in %s s\n", tag, nprimes,
count, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
}
我很清楚 srand()
— why call it once?,但每次调用此函数时使用一次 srand()
的目的是确保测试处理相同的随机数序列。在 macOS 上,RAND_MAX
是 0x7FFFFFFF
.
类型 Clock
包含两个 struct timespec
结构的类似物,用于开始和停止时间。 clk_init()
函数初始化结构; clk_start()
记录结构中的开始时间; clk_stop()
在结构体中记录停止时间; clk_elapsed_us()
以微秒为单位计算开始时间和停止时间之间经过的时间。该包旨在为我提供跨平台的可移植性(代价是在确定编译时可用的最佳亚秒级计时例程时遇到一些麻烦)。
您可以在存储库 https://github.com/jleffler/soq 的 Github 上找到我的计时器代码,在 src/libsoq
目录中 — 文件 timer.h
和 timer.c
。该代码尚未赶上具有 clock_gettime()
的 macOS Sierra,但可以将其编译为将其与 -DHAVE_CLOCK_GETTIME
作为命令行编译器选项一起使用。
此代码是从函数 one_test()
:
调用的
static void one_test(int seed)
{
printf("Seed; %d\n", seed);
enum { COUNT = 10000000 };
test_primality_tester("IsPrime1", seed, IsPrime1, COUNT);
test_primality_tester("IsPrime2", seed, IsPrime2, COUNT);
test_primality_tester("IsPrime3", seed, IsPrime3, COUNT);
test_primality_tester("isprime1", seed, isprime1, COUNT);
test_primality_tester("isprime2", seed, isprime2, COUNT);
test_primality_tester("isprime3", seed, isprime3, COUNT);
}
并且主程序可以取一个或一系列种子,或者使用当前时间作为种子:
int main(int argc, char **argv)
{
if (argc > 1)
{
for (int i = 1; i < argc; i++)
one_test(atoi(argv[i]));
}
else
one_test(time(0));
return(0);
}
是否有一种简单但可靠的方法来衡量 C 程序中两种算法实现之间的相对性能差异。更具体地说,我想比较实施 A 与 B 的性能?我正在考虑这样的方案:
在单元测试程序中:
- 启动计时器
- 调用函数
- 停止计时器
- 获取开始停止时间之间的差异
- 运行 上面的方案对于一对函数A和B,然后得到执行时间的百分比差异来确定哪个更快。
在做一些研究后,我遇到了这个关于使用 Monotonic clock on OSX in C, which apparently can give me at least nanosecond precision. To be clear, I understand that precise, controlled measurements are hard to perform, like what's discussed in
综合考虑,对于我想要执行的那种分析,这是一种充分有效的方法吗?有没有我可能遗漏的细节或注意事项?
我对您概述的时序方案所做的主要修改是通过将函数指针传递给骨架代码来确保两个函数使用相同的时序代码——假设它们确实具有相同的接口。
例如,我有一些代码可以对一些函数进行计时,这些函数可以验证给定数字是否为素数。控制函数为:
static void test_primality_tester(const char *tag, int seed, int (*prime)(unsigned), int count)
{
srand(seed);
Clock clk;
int nprimes = 0;
clk_init(&clk);
clk_start(&clk);
for (int i = 0; i < count; i++)
{
if (prime(rand()))
nprimes++;
}
clk_stop(&clk);
char buffer[32];
printf("%9s: %d primes found (out of %d) in %s s\n", tag, nprimes,
count, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
}
我很清楚 srand()
— why call it once?,但每次调用此函数时使用一次 srand()
的目的是确保测试处理相同的随机数序列。在 macOS 上,RAND_MAX
是 0x7FFFFFFF
.
类型 Clock
包含两个 struct timespec
结构的类似物,用于开始和停止时间。 clk_init()
函数初始化结构; clk_start()
记录结构中的开始时间; clk_stop()
在结构体中记录停止时间; clk_elapsed_us()
以微秒为单位计算开始时间和停止时间之间经过的时间。该包旨在为我提供跨平台的可移植性(代价是在确定编译时可用的最佳亚秒级计时例程时遇到一些麻烦)。
您可以在存储库 https://github.com/jleffler/soq 的 Github 上找到我的计时器代码,在 src/libsoq
目录中 — 文件 timer.h
和 timer.c
。该代码尚未赶上具有 clock_gettime()
的 macOS Sierra,但可以将其编译为将其与 -DHAVE_CLOCK_GETTIME
作为命令行编译器选项一起使用。
此代码是从函数 one_test()
:
static void one_test(int seed)
{
printf("Seed; %d\n", seed);
enum { COUNT = 10000000 };
test_primality_tester("IsPrime1", seed, IsPrime1, COUNT);
test_primality_tester("IsPrime2", seed, IsPrime2, COUNT);
test_primality_tester("IsPrime3", seed, IsPrime3, COUNT);
test_primality_tester("isprime1", seed, isprime1, COUNT);
test_primality_tester("isprime2", seed, isprime2, COUNT);
test_primality_tester("isprime3", seed, isprime3, COUNT);
}
并且主程序可以取一个或一系列种子,或者使用当前时间作为种子:
int main(int argc, char **argv)
{
if (argc > 1)
{
for (int i = 1; i < argc; i++)
one_test(atoi(argv[i]));
}
else
one_test(time(0));
return(0);
}