[英]CPU Cycle count based profiling in C/C++ Linux x86_64
我正在使用以下代码来分析我的操作,以优化我的函数中采用的 cpu 周期。
static __inline__ unsigned long GetCC(void)
{
unsigned a, d;
asm volatile("rdtsc" : "=a" (a), "=d" (d));
return ((unsigned long)a) | (((unsigned long)d) << 32);
}
我不认为它是最好的,因为即使连续两次调用也给我一个“33”的差异。 有什么建议吗?
我个人认为 rdtsc 指令很棒,可用于各种任务。 我不认为使用cpuid来准备rdtsc是必要的。 以下是我对 rdtsc 的推理:
至于时间戳计数器是否准确的问题,我会说假设不同内核上的 tsc 是同步的(这是常态),在低活动期间存在 CPU 节流以降低能耗的问题。 测试时总是可以禁止该功能。 如果您在同一处理器上以 1 GHz 或 10 Mhz 的频率执行指令,则经过的周期计数将相同,即使前者在 1% 的时间内完成,后者也比后者。
尝试计算单个函数执行的周期并不是真正正确的方法。 您的进程可能随时被中断,加上缓存未命中和分支预测错误导致的延迟,这意味着从调用到调用的周期数可能存在相当大的偏差。
正确的方法是:
clock()
),然后对它们求平均值; 或 顺便说一句,您需要在RDTSC
之前执行序列化指令。 通常使用CPUID
。
你在正确的轨道上1 ,但你需要做两件事:
rdtsc
之前运行cpuid
指令以刷新 CPU 管道(使测量更可靠)。 据我回忆,clobbers 寄存器从eax
到edx
。gettimeofday
(Linux,因为您没有提到平台)调用和rdtsc
输出的测量值有所不同。 然后你可以知道每个 TSC 滴答需要多少时间。 另一个考虑因素是跨 CPU 的 TSC 同步,因为每个内核可能都有自己的计数器。 在 Linux 中,您可以在/proc/cpuinfo
看到它,您的 CPU 应该有一个constant_tsc
标志。 我见过的大多数较新的 Intel CPU 都有这个标志。 1我个人发现rdtsc
比gettimeofday()
等系统调用更准确,用于细粒度测量。
您可能需要担心的另一件事是,如果您在多核机器上运行,程序可能会移动到不同的内核,该内核将具有不同的 rdtsc 计数器。 不过,您可以通过系统调用将进程固定到一个核心。
如果我试图测量这样的东西,我可能会将时间戳记录到一个数组中,然后在被基准测试的代码完成后回来检查这个数组。 当你检查记录到时间戳数组的数据时,你应该记住这个数组将依赖于 CPU 缓存(如果你的数组很大,可能会分页),但你可以预取或在分析时记住这一点数据。 您应该会看到时间戳之间非常规律的时间增量,但有几个尖峰和可能的几个低谷(可能是由于移动到不同的核心)。 常规时间增量可能是您最好的测量,因为它表明没有外部事件影响这些测量。
话虽如此,如果您进行基准测试的代码具有不规则的内存访问模式或运行时间或依赖于系统调用(尤其是与 IO 相关的调用),那么您将很难将噪声与您感兴趣的数据分开。
TSC 不是衡量时间的好方法。 CPU 对 TSC 做出的唯一保证是它单调上升(也就是说,如果您RDTSC
一次然后再次执行,则第二个将返回一个高于第一个的结果)并且它将把它作为很长的时间来环绕。
Linux perf_event_open
系统调用, config = PERF_COUNT_HW_CPU_CYCLES
这个 Linux 系统调用似乎是性能事件的跨架构包装器。
这个答案与这个 C++ 问题的答案基本相同: 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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.