我正在构建一个微基准来测量性能变化,因为我在一些原始图像处理操作中尝试使用SIMD指令内在函数。但是,编写有用的微基准测试很困难,因此我想首先了解(并尽可能消除)尽可能多的变异和误差来源。
我必须考虑的一个因素是测量代码本身的开销。我正在使用RDTSC进行测量,我正在使用以下代码来查找测量开销:
extern inline unsigned long long __attribute__((always_inline)) rdtsc64() {
unsigned int hi, lo;
__asm__ __volatile__(
"xorl %%eax, %%eax\n\t"
"cpuid\n\t"
"rdtsc"
: "=a"(lo), "=d"(hi)
: /* no inputs */
: "rbx", "rcx");
return ((unsigned long long)hi << 32ull) | (unsigned long long)lo;
}
unsigned int find_rdtsc_overhead() {
const int trials = 1000000;
std::vector<unsigned long long> times;
times.resize(trials, 0.0);
for (int i = 0; i < trials; ++i) {
unsigned long long t_begin = rdtsc64();
unsigned long long t_end = rdtsc64();
times[i] = (t_end - t_begin);
}
// print frequencies of cycle counts
}
运行此代码时,我得到如下输出:
Frequency of occurrence (for 1000000 trials):
234 cycles (counted 28 times)
243 cycles (counted 875703 times)
252 cycles (counted 124194 times)
261 cycles (counted 37 times)
270 cycles (counted 2 times)
693 cycles (counted 1 times)
1611 cycles (counted 1 times)
1665 cycles (counted 1 times)
... (a bunch of larger times each only seen once)
我的问题是这些:
更多信息
平台:
SpeedStep已关闭(处理器设置为性能模式,运行速度为2.4GHz);如果在“按需”模式下运行,我会在243和252个周期获得两个峰值,并在360和369个周期获得两个(可能是相应的)峰值。
我正在使用sched_setaffinity
将进程锁定到一个核心。如果我依次在每个核心上运行测试(即,锁定到核心0并运行,然后锁定到核心1并运行),我得到两个核心的类似结果,除了234个循环的最快时间往往略有发生核心1的次数少于核心0的次数。
编译命令是:
g++ -Wall -mssse3 -mtune=core2 -O3 -o test.bin test.cpp
GCC为核心循环生成的代码是:
.L105:
#APP
# 27 "test.cpp" 1
xorl %eax, %eax
cpuid
rdtsc
# 0 "" 2
#NO_APP
movl %edx, %ebp
movl %eax, %edi
#APP
# 27 "test.cpp" 1
xorl %eax, %eax
cpuid
rdtsc
# 0 "" 2
#NO_APP
salq $32, %rdx
salq $32, %rbp
mov %eax, %eax
mov %edi, %edi
orq %rax, %rdx
orq %rdi, %rbp
subq %rbp, %rdx
movq %rdx, (%r8,%rsi)
addq $8, %rsi
cmpq $8000000, %rsi
jne .L105
答案 0 :(得分:7)
RDTSC
可能会因多种原因返回不一致的结果:
sched_setaffinity
- 好了!times[]
)的速度可能会有所不同。在这种情况下,您很幸运,正在使用的std::vector
实现只是一个扁平数组;即便如此,这种写作也会让事情发生。这可能是此代码最重要的因素。我对Core2微体系结构的大师来说,还不足以说明为什么你会得到这种双峰分布,或者你的代码如何更快地运行28次,但它可能与上述原因之一有关
答案 1 :(得分:2)
如果您想确保lfence;rdtsc
之前的指示已实际执行,则英特尔程序员手册建议您使用rdtscp
或rdtsc
。这是因为rdtsc
本身不是序列化指令。
答案 2 :(得分:1)
您应确保在操作系统级别禁用频率限制/绿色功能。重启机器。否则,您可能会遇到内核具有不同步的时间戳计数器值的情况。
243读数是迄今为止最常见的,这是使用它的一个原因。另一方面,假设您得到一个经过的时间&lt; 249:您减去开销并获得下溢。由于算术是无符号的,因此最终得到了巨大的结果。这个事实代表使用最低读数(243)。准确测量只有几个周期的序列是非常困难的。在典型的x86 @几GHz上,我建议不要使用短于10ns的时序,即使在那个长度,它们通常也会远离坚如磐石。
我在这里的其余部分是我的工作,我如何处理结果以及我对主题的推理。
关于开销,最简单的方法是使用这样的代码
unsigned __int64 rdtsc_inline (void);
unsigned __int64 rdtsc_function (void);
第一种形式将rdtsc指令发送到生成的代码中(如代码中所示)。第二个将导致调用函数,执行rdtsc和返回指令。也许它会生成堆栈帧。显然,第二种形式比第一种形式慢得多。
然后可以编写用于开销计算的(C)代码
unsigned __int64 start_cycle,end_cycle; /* place these @ the module level*/
unsigned __int64 overhead;
/* place this code inside a function */
start_cycle=rdtsc_inline();
end_cycle=rdtsc_inline();
overhead=end_cycle-start_cycle;
如果您使用内联变体,您将获得较低的开销。您还将面临计算大于“应该”的开销的风险(特别是对于函数形式),这反过来意味着如果您测量非常短/快的序列,您可能会遇到先前计算的开销,即大于测量本身。当您尝试调整开销时,您将获得下溢,这将导致混乱的条件。处理此问题的最佳方法是
我之前已经在this thread讨论了我对结果的处理方式。
我要做的另一件事是将测量代码集成到应用程序中。开销微不足道。在计算结果后,我将其发送到一个特殊的结构,在那里我计算测量数量,总和x和x ^ 2值并确定最小和最大测量值。稍后我可以使用这些数据来计算平均值和标准差。结构本身是索引的,我可以测量不同的性能方面,如单独的应用程序功能(“功能性能”),在cpu中花费的时间,磁盘读/写,网络读/写(“非功能性能”)等。
如果应用程序以这种方式进行检测并从一开始就受到监控,我预计它在生命周期内出现性能问题的风险将大大降低。