我试图找出是否有任何想法来了解我的C代码正在运行的系统的CPU频率.
为了澄清,我正在寻找一个抽象的解决方案(一个不会与特定架构或操作系统绑定的解决方案),它可以让我了解我的代码正在执行的计算机的运行频率.我不需要准确,但我想进入球场(即我有一个2.2GHz处理器,我希望能够在我的程序中告诉我我在几百之内)那个MHz)
有没有人有想法使用标准C代码?
Z b*_*son 10
可以找到一个通用解决方案,它可以正确地为一个线程或多个线程获取工作频率.这不需要管理员/ root权限或访问模型特定的寄存器.我已经在Linux和Windows上对英特尔处理器进行了测试,包括Nahalem,Ivy Bridge和Haswell,一个插槽最多四个插槽(40个线程).结果与正确答案的偏差均小于0.5%.在我向您展示如何做之前,让我展示一下结果(来自GCC 4.9和MSVC2013):
Linux: E5-1620 (Ivy Bridge) @ 3.60GHz
1 thread: 3.789, 4 threads: 3.689 GHz: (3.8-3.789)/3.8 = 0.3%, 3.7-3.689)/3.7 = 0.3%
Windows: E5-1620 (Ivy Bridge) @ 3.60GHz
1 thread: 3.792, 4 threads: 3.692 GHz: (3.8-3.789)/3.8 = 0.2%, (3.7-3.689)/3.7 = 0.2%
Linux: 4xE7-4850 (Nahalem) @ 2.00GHz
1 thread: 2.390, 40 threads: 2.125 GHz:, (2.4-2.390)/2.4 = 0.4%, (2.133-2.125)/2.133 = 0.4%
Linux: i5-4250U (Haswell) CPU @ 1.30GHz
1 thread: within 0.5% of 2.6 GHz, 2 threads wthin 0.5% of 2.3 GHz
Windows: 2xE5-2667 v2 (Ivy Bridge) @ 3.3 GHz
1 thread: 4.000 GHz, 16 threads: 3.601 GHz: (4.0-4.0)/4.0 = 0.0%, (3.6-3.601)/3.6 = 0.0%
Run Code Online (Sandbox Code Playgroud)
我从这个链接中得到了这个想法 http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/
要做到这一点,你首先要做的就是你20年前做的事情.你用循环编写一些代码,你知道它的延迟和时间.这是我用过的:
static int inline SpinALot(int spinCount)
{
__m128 x = _mm_setzero_ps();
for(int i=0; i<spinCount; i++) {
x = _mm_add_ps(x,_mm_set1_ps(1.0f));
}
return _mm_cvt_ss2si(x);
}
Run Code Online (Sandbox Code Playgroud)
这具有承载循环依赖性,因此CPU无法对此进行重新排序以减少延迟.每次迭代总是需要3个时钟周期.操作系统不会将线程迁移到另一个核心,因为我们将绑定线程.
然后在每个物理核心上运行此功能.我用OpenMP做到了这一点.必须为此绑定线程.在使用GCC的linux中,您可以使用export OMP_PROC_BIND=true绑定线程并假设您具有ncores物理核心export OMP_NUM_THREADS=ncores.如果您想以编程方式绑定并查找英特尔处理器的物理内核数量,请参阅此编程检测数量的物理处理器核心或如果超线程和线程关联性与Windows-msvc-和 - openmp.
void sample_frequency(const int nsamples, const int n, float *max, int nthreads) {
*max = 0;
volatile int x = 0;
double min_time = DBL_MAX;
#pragma omp parallel reduction(+:x) num_threads(nthreads)
{
double dtime, min_time_private = DBL_MAX;
for(int i=0; i<nsamples; i++) {
#pragma omp barrier
dtime = omp_get_wtime();
x += SpinALot(n);
dtime = omp_get_wtime() - dtime;
if(dtime<min_time_private) min_time_private = dtime;
}
#pragma omp critical
{
if(min_time_private<min_time) min_time = min_time_private;
}
}
*max = 3.0f*n/min_time*1E-9f;
}
Run Code Online (Sandbox Code Playgroud)
最后在循环中运行采样器并打印结果
int main(void) {
int ncores = getNumCores();
printf("num_threads %d, num_cores %d\n", omp_get_max_threads(), ncores);
while(1) {
float max1, median1, max2, median2;
sample_frequency(1000, 1000000, &max2, &median2, ncores);
sample_frequency(1000, 1000000, &max1, &median1,1);
printf("1 thread: %.3f, %d threads: %.3f GHz\n" ,max1, ncores, max2);
}
}
Run Code Online (Sandbox Code Playgroud)
我没有在AMD处理器上测试过这个.我认为带有模块的AMD处理器(例如Bulldozer)必须绑定到每个模块而不是每个AMD"核心".这可以通过export GOMP_CPU_AFFINITYGCC 来完成.您可以在https://bitbucket.org/zboson/frequency找到一个完整的工作示例,它可以在英特尔处理器上的Windows和Linux上运行,并且可以正确地找到英特尔处理器的物理内核数量(至少自Nahalem以来)并将它们绑定到每个物理核心(不使用OMP_PROC_BIND哪个MSVC没有).
由于SSE,AVX和AVX512的频率调整不同,这种方法必须针对现代处理器进行一些修改.
这是我在修改我的方法(参见表后代码)后得到的一个新表,它有四个Xeon 6142处理器(每个处理器16个内核).
sums 1-thread 64-threads
SSE 1 3.7 3.3
SSE 8 3.7 3.3
AVX 1 3.7 3.3
AVX 2 3.7 3.3
AVX 4 3.6 2.9
AVX 8 3.6 2.9
AVX512 1 3.6 2.9
AVX512 2 3.6 2.9
AVX512 4 3.5 2.2
AVX512 8 3.5 2.2
Run Code Online (Sandbox Code Playgroud)
这些数字与此表中的频率一致 https://en.wikichip.org/wiki/intel/xeon_gold/6142#Frequencies
有趣的是,我现在需要至少做4个并行求和来实现更低的频率.Skylake上的addps延迟是4个时钟周期.这些可以转到两个端口(AVX512端口0和1保险丝计数,一个AVX512端口,其他AVX512操作转到端口5).
这是我如何做八个平行和.
static int inline SpinALot(int spinCount) {
__m512 x1 = _mm512_set1_ps(1.0);
__m512 x2 = _mm512_set1_ps(2.0);
__m512 x3 = _mm512_set1_ps(3.0);
__m512 x4 = _mm512_set1_ps(4.0);
__m512 x5 = _mm512_set1_ps(5.0);
__m512 x6 = _mm512_set1_ps(6.0);
__m512 x7 = _mm512_set1_ps(7.0);
__m512 x8 = _mm512_set1_ps(8.0);
__m512 one = _mm512_set1_ps(1.0);
for(int i=0; i<spinCount; i++) {
x1 = _mm512_add_ps(x1,one);
x2 = _mm512_add_ps(x2,one);
x3 = _mm512_add_ps(x3,one);
x4 = _mm512_add_ps(x4,one);
x5 = _mm512_add_ps(x5,one);
x6 = _mm512_add_ps(x6,one);
x7 = _mm512_add_ps(x7,one);
x8 = _mm512_add_ps(x8,one);
}
__m512 t1 = _mm512_add_ps(x1,x2);
__m512 t2 = _mm512_add_ps(x3,x4);
__m512 t3 = _mm512_add_ps(x5,x6);
__m512 t4 = _mm512_add_ps(x7,x8);
__m512 t6 = _mm512_add_ps(t1,t2);
__m512 t7 = _mm512_add_ps(t3,t4);
__m512 x = _mm512_add_ps(t6,t7);
return _mm_cvt_ss2si(_mm512_castps512_ps128(x));
}
Run Code Online (Sandbox Code Playgroud)
Use*_*973 10
为了完整起见,已经有一个简单,快速,准确的用户模式解决方案,具有巨大的缺点:它仅适用于Intel Skylake,Kabylake和更新的处理器.确切的要求是CPUID级别16h支持.根据英特尔软件开发人员手册325462第59版,第770页:
CPUID.16h.EAX =处理器基频(MHz);
CPUID.16h.EBX =最大频率(MHz);
CPUID.16h.ECX =总线(参考)频率(以MHz为单位).
Visual Studio 2015示例代码:
#include <stdio.h>
#include <intrin.h>
int main(void) {
int cpuInfo[4] = { 0, 0, 0, 0 };
__cpuid(cpuInfo, 0);
if (cpuInfo[0] >= 0x16) {
__cpuid(cpuInfo, 0x16);
//Example 1
//Intel Core i7-6700K Skylake-H/S Family 6 model 94 (506E3)
//cpuInfo[0] = 0x00000FA0; //= 4000 MHz
//cpuInfo[1] = 0x00001068; //= 4200 MHz
//cpuInfo[2] = 0x00000064; //= 100 MHz
//Example 2
//Intel Core m3-6Y30 Skylake-U/Y Family 6 model 78 (406E3)
//cpuInfo[0] = 0x000005DC; //= 1500 MHz
//cpuInfo[1] = 0x00000898; //= 2200 MHz
//cpuInfo[2] = 0x00000064; //= 100 MHz
//Example 3
//Intel Core i5-7200 Kabylake-U/Y Family 6 model 142 (806E9)
//cpuInfo[0] = 0x00000A8C; //= 2700 MHz
//cpuInfo[1] = 0x00000C1C; //= 3100 MHz
//cpuInfo[2] = 0x00000064; //= 100 MHz
printf("EAX: 0x%08x EBX: 0x%08x ECX: %08x\r\n", cpuInfo[0], cpuInfo[1], cpuInfo[2]);
printf("Processor Base Frequency: %04d MHz\r\n", cpuInfo[0]);
printf("Maximum Frequency: %04d MHz\r\n", cpuInfo[1]);
printf("Bus (Reference) Frequency: %04d MHz\r\n", cpuInfo[2]);
} else {
printf("CPUID level 16h unsupported\r\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
您如何找到CPU频率取决于体系结构和操作系统,并且没有抽象的解决方案.
如果我们20多年前你使用的是没有上下文切换的操作系统并且CPU按顺序执行给出的指令,你可以在循环中编写一些C代码并计时,然后根据汇编编译成的程序集计算运行时的指令数.这已经假设每条指令需要1个时钟周期,这是自流水线处理器以来的一个相当差的假设.
但任何现代操作系统都会在多个进程之间切换 即使这样,您也可以尝试计算一堆相同的for循环运行时间(忽略页面错误所需的时间以及处理器可能停止的其他多种原因)并获得中值.
即使以前的解决方案有效,您也可以使用多个处理器.使用任何现代处理器,重新订购指令,在同一时钟周期内发布一堆指令,甚至在核心之间拆分它们都是公平的游戏.