在C中测量程序循环次数的最可靠方法是什么?

时间:2016-03-10 17:55:31

标签: c performance clock rdtsc

我熟悉两种方法,但它们都有其局限性。

第一个是使用指令RDTSC。然而,问题是它不能单独计算我的程序的周期数,因此对并发进程产生的噪声很敏感。

第二个选项是使用clock库函数。我认为这种方法是可靠的,因为我希望它只计算我的程序的周期数(我打算实现)。但是,事实证明,在我的情况下,它测量经过的时间,然后乘以CLOCKS_PER_SEC。这不仅不可靠,而且也是错误的,因为CLOCKS_PER_SEC设置为1,000,000,这与我的处理器的实际频率不对应。

鉴于所提方法的局限性,是否有更好,更可靠的替代方法来产生一致的结果?

3 个答案:

答案 0 :(得分:2)

这里有很多取决于你尝试测量的时间量。

正确使用时,RDTSC可以(几乎)100%可靠。然而,它主要用于测量真正的微观代码片段。如果你想要测量两个序列,比如几十个左右的指令,那么可能还有其他任何东西都可以完成这项工作。

正确使用它有点挑战性。一般来说,为了获得良好的测量结果,您至少需要做以下几点:

  1. 将代码设置为仅在一个特定核心上运行。
  2. 将代码设置为以最高优先级执行,因此没有任何优先级。
  3. 大量使用CPUID以确保在需要时进行序列化。
  4. 另一方面,如果您尝试测量的东西,例如100毫秒,那么RDTSC毫无意义。这就像用千分尺测量城市之间的距离一样。为此,通常最好确保所讨论的代码(至少)花费大约一秒左右的时间。 clock并不是特别精确,但是对于这个一般订单的一段时间,它可能只有10毫秒左右才准确,这或多或少是不相关的。

答案 1 :(得分:0)

RDTSC是计算程序执行周期的最准确方法。如果您希望在时间范围内测量执行性能,如果您的线程已被抢占,那么您可能会更好地使用分析器(例如VTune)。

与RDTSC相比,CLOCKS_PER_SECOND / clock()几乎是一种非常糟糕(低性能)的获取时间的方式,而RDTSC几乎没有开销。

如果您对RDTSC有特定问题,我可能会提供帮助。

re:评论

英特尔性能计数器监视器:这主要用于测量处理器之外的指标,例如内存带宽,电源使用情况,PCIe利用率。它也会测量CPU频率,但它通常对处理器绑定的应用程序性能没有用。

RDTSC 可移植性:RDTSC是所有现代Intel CPU都支持的intel CPU指令。在现代CPU上,它基于CPU的非核心频率,并且在CPU核心上有些相似,尽管如果您的应用程序经常被抢占到不同的核心(尤其是不同的套接字),这是不合适的。如果是这种情况,你真的想看一个分析器。

乱序执行:是的,事情无序执行,因此这可能会略微影响性能,但执行指令仍需要时间,而RDTSC是衡量时间的最佳方法。它在同一核心上执行非IO绑定指令的正常使用情况方面表现优异,这实际上就是它的用途。如果你有一个更复杂的用例,你真的应该使用不同的工具,但这并不能否定rdtsc()在分析程序执行时非常有用。

答案 2 :(得分:0)

使用perf_event_open的Linux config = PERF_COUNT_HW_CPU_CYCLES系统调用

此系统调用具有以下明确控件:

  • 过程PID选择
  • 是否考虑内核/管理程序指令

,因此即使多个进程同时运行,它也可以正确计数周期。

有关更多详细信息,请参见以下答案:How to get the CPU cycle count in x86_64 from C++?

perf_event_open.c

#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <inttypes.h>

static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                int cpu, int group_fd, unsigned long flags)
{
    int ret;

    ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                    group_fd, flags);
    return ret;
}

int
main(int argc, char **argv)
{
    struct perf_event_attr pe;
    long long count;
    int fd;

    uint64_t n;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 10000;
    }

    memset(&pe, 0, sizeof(struct perf_event_attr));
    pe.type = PERF_TYPE_HARDWARE;
    pe.size = sizeof(struct perf_event_attr);
    pe.config = PERF_COUNT_HW_CPU_CYCLES;
    pe.disabled = 1;
    pe.exclude_kernel = 1;
    // Don't count hypervisor events.
    pe.exclude_hv = 1;

    fd = perf_event_open(&pe, 0, -1, -1, 0);
    if (fd == -1) {
        fprintf(stderr, "Error opening leader %llx\n", pe.config);
        exit(EXIT_FAILURE);
    }

    ioctl(fd, PERF_EVENT_IOC_RESET, 0);
    ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

    /* Loop n times, should be good enough for -O0. */
    __asm__ (
        "1:;\n"
        "sub $1, %[n];\n"
        "jne 1b;\n"
        : [n] "+r" (n)
        :
        :
    );

    ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
    read(fd, &count, sizeof(long long));

    printf("%lld\n", count);

    close(fd);
}