C语言 为什么我的 CPU 突然以两倍的速度工作



我一直在尝试使用一个简单的分析器来测量学校服务器上某些 C 代码的效率,但我遇到了一个奇怪的情况。在短时间(半秒左右)后,处理器突然开始以两倍的速度执行指令。我已经测试了几乎所有我能想到的原因(缓存、内核上的负载平衡、CPU 频率因从睡眠状态而改变),但一切似乎都很正常。

值得一提的是,我正在学校的 linux 服务器上进行此测试,因此可能存在我不知道的异常配置,但使用的处理器 ID 不会更改,并且(通过顶部)服务器在我测试时完全空闲。

测试代码:

#include <time.h>
#include <stdio.h>
#define MY_CLOCK CLOCK_MONOTONIC_RAW
// no difference if set to CLOCK_THREAD_CPUTIME_ID
typedef struct {
        unsigned int tsc;
        unsigned int proc;
} ans_t;
static ans_t rdtscp(void){
        ans_t ans;
        __asm__ __volatile__ ("rdtscp" : "=a"(ans.tsc), "=c"(ans.proc) : : "edx");
        return ans;
}
static void nop(void){
        __asm__ __volatile__ ("");
}
void test(){
        for(int i=0; i<100000000; i++) nop();
}
int main(){
        int c=10;
        while(c-->0){
                struct timespec tstart,tend;
                ans_t start = rdtscp();
                clock_gettime(MY_CLOCK,&tstart);
                test();
                ans_t end = rdtscp();
                clock_gettime(MY_CLOCK,&tend);
                unsigned int tdiff = (tend.tv_sec-tstart.tv_sec)*1000000000+tend.tv_nsec-tstart.tv_nsec;
                unsigned int cdiff = end.tsc-start.tsc;
                printf("%u cycles and %u ns (%lf GHz) start proc %u end proc %un",cdiff,tdiff,(double)cdiff/tdiff,start.proc,end.proc);
        }
}

我看到的输出:

351038093 cycles and 125680883 ns (2.793091 GHz) start proc 14 end proc 14
350911246 cycles and 125639359 ns (2.793004 GHz) start proc 14 end proc 14
350959546 cycles and 125656776 ns (2.793001 GHz) start proc 14 end proc 14
351533280 cycles and 125862608 ns (2.792992 GHz) start proc 14 end proc 14
350903833 cycles and 125636787 ns (2.793002 GHz) start proc 14 end proc 14
350924336 cycles and 125644157 ns (2.793002 GHz) start proc 14 end proc 14
349827908 cycles and 125251782 ns (2.792997 GHz) start proc 14 end proc 14
175289886 cycles and 62760404 ns (2.793001 GHz) start proc 14 end proc 14
175283424 cycles and 62758093 ns (2.793001 GHz) start proc 14 end proc 14
175267026 cycles and 62752232 ns (2.793001 GHz) start proc 14 end proc 14

我使用不同的优化级别(-O0 到 -O3)获得类似的输出(需要不同数量的测试才能使效率翻倍)。

它是否可能与超线程有关,其中物理内核中的两个逻辑内核(服务器正在使用Xeon X5560,可能会产生这种效果)可以以某种方式"合并"以形成一个速度快两倍的处理器?

某些系统根据系统负载扩展处理器速度。 正如您刚刚指出的那样,这在基准测试时特别烦人。

如果您的服务器运行的是 Linux,请输入

cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 

如果这输出ondemandpowersaveuserspace,则 CPU 频率缩放处于活动状态,您会发现很难进行基准测试。 如果这显示performance,则禁用 CPU 频率缩放。

一些CPU在芯片上进行了优化,这些优化正在学习代码通常采用的路径。通过成功预测下一个 if 语句将执行的操作,不需要丢弃队列,并从头开始重新加载所有新操作。根据芯片和算法的不同,可能需要 5 到 10 个周期,直到成功预测 if 语句。但不知何故,也有理由反对这一点,认为这是这种行为的原因。

看着你的输出,我会说这可能只是操作系统和/或那里使用的CPU频率调节器的脱落。您确定 CPU 频率在代码执行过程中不会改变吗?没有 CPU 提升?使用像cpufreq这样的Linux工具通常用于调节CPU频率。

超线程意味着复制寄存器空间,而不是实际的解码/执行单元 - 所以这不是一个解决方案。

为了测试微基准测试方法的准确性,我将执行以下操作:

  1. 以高优先级运行程序
  2. 计算指令的数量,看看它是否正确。我会使用 perf stat ./binary 来做到这一点 - 这意味着你需要有 perf。我会多次执行此操作,并查看时钟和指令指标,以了解多个指令如何在单个周期中执行。

我还有一些补充意见

对于每个nop,您还可以在for循环中进行比较和条件跳转。如果你真的想执行NOP,我会写一个这样的语句:

#define NOP5 __asm__ __volatile__ ("nop nop nop nop nop");
#define NOP25 NOP5 NOP5 NOP5 NOP5 NOP5
#define NOP100 NOP25 NOP25 NOP25 NOP25
#define NOP500 NOP100 NOP100 NOP100 NOP100 NOP100
...
for(int i=0; i<100000000; i++)
{
   NOP500 NOP500 NOP500 NOP500
}

这种结构将允许您实际执行NOP,而不是将i与100M进行比较

最新更新