我正在使用以下代码来分析我的操作,以优化我的函数中的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”的差异。 有什么建议吗?
答案 0 :(得分:5)
我个人认为rdtsc指令很棒,可用于各种任务。我不认为使用cpuid是准备rdtsc所必需的。以下是我对rdtsc的理由:
关于时间戳计数器准确的问题,我会说假设不同核心上的tsc是同步的(这是常态),在低活动期间存在CPU节流的问题以减少能量消耗。测试时始终可以禁用功能。如果您在同一处理器上执行1 GHz或10 Mhz的指令,则经过的周期计数将是相同的,即使前者在1%的时间内完成并且与后者相符。
答案 1 :(得分:2)
尝试计算单个函数执行的周期并不是正确的方法。您的进程可能随时被中断,以及缓存未命中和分支错误预测导致的延迟这一事实意味着从呼叫到呼叫的周期数可能存在相当大的偏差。
正确的方法是:
clock()
),然后对它们取平均值;或顺便说一句,您需要在RDTSC
之前执行序列化指令。通常使用CPUID
。
答案 2 :(得分:2)
你走在正确的轨道 1 ,但你需要做两件事:
cpuid
之前运行rdtsc
指令以刷新CPU管道(使测量更可靠)。据我所知,它从eax
注册到edx
。gettimeofday
(Linux,因为您没有提到平台)调用和rdtsc
输出。然后你可以告诉每个TSC滴答需要多少时间。另一个考虑因素是跨CPU的TSC同步,因为每个核心可能有自己的计数器。在Linux中,您可以在/proc/cpuinfo
中看到它,您的CPU应该有一个constant_tsc
标志。我见过的大多数较新的Intel CPU都有这个标志。 1 我个人发现rdtsc
比gettimeofday()
等系统调用更精确,可用于细粒度测量。
答案 3 :(得分:2)
您可能需要担心的另一件事是,如果您在多核计算机上运行,程序可能会移动到另一个核心,该核心将具有不同的rdtsc计数器。但是,您可以通过系统调用将进程固定到一个核心。
如果我试图测量这样的东西,我可能会将时间戳记录到数组中,然后在完成基准测试的代码之后再回来检查这个数组。当您检查记录到时间戳数组的数据时,您应该记住,此数组将依赖于CPU缓存(如果您的数组很大,可能会进行分页),但您可以预取或在分析时记住这一点数据。您应该在时间戳之间看到非常规则的时间差,但是有几个尖峰,可能有几个下降(可能是从移动到不同的核心)。常规时间增量可能是您的最佳测量值,因为它表明没有外部事件影响这些测量。
话虽如此,如果您进行基准测试的代码具有不规则的内存访问模式或运行时间或依赖于系统调用(尤其是与IO相关的代码),那么您将很难将噪声与您感兴趣的数据分开。
答案 4 :(得分:1)
TSC不是一个很好的时间衡量标准。 CPU对TSC的唯一保证是它单调上升(也就是说,如果你RDTSC
一次然后再次执行,第二个将返回一个高于第一个的结果)并且它将用很长时间来解决。
答案 5 :(得分:0)
我是否理解为什么你这样做的原因是用它来包含其他代码,以便你可以测量其他代码需要多长时间?
我相信你知道另一个好办法就是循环其他代码10 ^ 6次,秒表它,并称之为微秒。
一旦你测量了其他代码,我是否正确地假设您想知道哪些行值得优化,以减少所需的时间?
如果是这样的话,那你就处于良好的状态。您可以使用Zoom或LTProf等工具。这是my favorite method.
答案 6 :(得分:0)
使用perf_event_open
的Linux 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);
}