【问题标题】:Relative C code execution time analysis using system monotonic clock使用系统单调时钟的相对 C 代码执行时间分析
【发布时间】:2017-04-30 11:16:06
【问题描述】:

是否有一种简单但可靠的方法来衡量 C 程序中两种算法实现之间的性能相对差异。更具体地说,我想比较实现 A 与 B 的性能?我正在考虑这样的方案:

  • 在单元测试程序中:

    1. 启动计时器
    2. 调用函数
    3. 停止计时器
    4. 获取开始停止时间之间的差异
  • 为一对函数 A 和 B 运行上述方案,然后获取执行时间的百分比差异以确定哪个更快。

在做一些研究时,我遇到了这个关于在 C 中使用 Monotonic clock on OSX 的问题,这显然可以给我至少纳秒的精度。需要明确的是,我知道很难执行精确、受控的测量,就像"With O(N) known and system clock known, can we calculate the execution time of the code? 中讨论的那样,我认为在这种情况下应该无关紧要,因为我只想要一个相对测量。

考虑到所有因素,对于我想要执行的分析,这是一种充分且有效的方法吗?是否有任何我可能遗漏的细节或注意事项?

【问题讨论】:

  • 如果你在 macOS Sierra (10.12) 或更高版本上工作,那么系统终于提供了对clock_gettime() 的支持,虽然真正的分辨率只有微秒,而不是纳秒。 (也就是说,clock_gettime() 的答案的 tv_nsec 部分始终以三个零结尾。)您引用的问题比 Sierra 早了四年多。
  • 好吧,我正在使用 10.11,并且来自参考的 mach 标头函数调用仍然有效。但这很高兴知道。

标签: c linux macos profiling clock


【解决方案1】:

我对您概述的时序方案所做的主要修改是确保两个函数使用相同的时序代码——假设它们确实具有相同的接口,通过将函数指针传递给骨架代码。

例如,我有一些代码对验证给定数字是否为素数的一些函数计时。控制函数为:

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_MAX0x7FFFFFFF

Clock 类型包含两个 struct timespec 结构的类似物,用于开始和停止时间。 clk_init() 函数初始化结构体; clk_start() 记录结构中的开始时间; clk_stop()记录结构中的停止时间; clk_elapsed_us() 计算开始和停止时间之间经过的时间(以微秒为单位)。编写该软件包的目的是为我提供跨平台的可移植性(在确定编译时可用的最佳亚秒级计时例程时有些头疼)。

你可以在 Github 上的代码库 https://github.com/jleffler/soqsrc/libsoq 目录中找到我的计时器代码——文件 timer.htimer.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);
}

【讨论】:

  • 感谢您的回复,它验证了我的想法。您在 libsoq 目录中指出的示例很清楚,但我没有看到任何用于单调时钟配置的设置代码。我假设我可以只使用 COCK_MONOTONIC 定义而不是 CLOCK_REALTIME 对吗?
  • 你是对的。在 Sierra 之前,我没有一台可以选择 CLOCK_MONOTONIC 的机器——我主要使用gettimeofday()——所以这对我来说不是什么大问题。这是我的“工作代码”;对我的目的来说足够好,但不一定完美。 CLOCK_MONOTONIC 的一个缺点是它没有给你一个绝对的时间戳(相对于时代 - 又名 1970-01-01 00:00:00 +00:00),而 gettimeofday() 有。但是对于性能测量,您很少担心运行时间达到与运行时间相同的精度水平。
猜你喜欢
  • 1970-01-01
  • 2021-12-10
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-20
  • 2020-08-15
相关资源
最近更新 更多