常用于测量处理器频率的算法有哪些?
Core Duo之后的Intel CPU支持两个名为IA32_MPERF和IA32_APERF的模型特定寄存器。
MPERF计数最大频率,而APERF计数实际当前频率。
实际频率由以下公式给出:
您可以使用此流程读取它们。
; read MPERF
mov ecx, 0xe7
rdmsr
mov mperf_var_lo, eax
mov mperf_var_hi, edx
; read APERF
mov ecx, 0xe8
rdmsr
mov aperf_var_lo, eax
mov aperf_var_hi, edx
但需要注意的是,rdmsr是一条特权指令,只能在Ring 0运行。
我不知道操作系统是否提供了读取这些寄存器的接口,虽然它们主要用于功耗管理,因此可能不提供这样的接口。
好的,我现在会回去赶孩子离开我的草坪了。
在英特尔CPU(自Pentium以来)上的一种方法是使用两个RDTSC指令的采样,配合已知墙钟时间的延迟循环,例如:
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
uint64_t rdtsc(void) {
uint64_t result;
__asm__ __volatile__ ("rdtsc" : "=A" (result));
return result;
}
int main(void) {
uint64_t ts0, ts1;
ts0 = rdtsc();
sleep(1);
ts1 = rdtsc();
printf("clock frequency = %llu\n", ts1 - ts0);
return 0;
}
(在使用GCC的32位平台上)
如果CR4中的TSC标志被设置,那么RDTSC可在ring 3中使用,这是常见但不保证的。这种方法的一个缺点是,如果频率缩放改变了结果,它就容易受到影响。为了缓解这个问题,你可以执行一些代码来使CPU保持繁忙状态,并不断轮询系统时间以查看延迟期是否已过期,从而使CPU处于最高可用频率状态。
我使用以下(伪)算法:
basetime=time(); /* time returns seconds */
while (time()==basetime);
stclk=rdtsc(); /* rdtsc is an assembly instruction */
basetime=time();
while (time()==basetime
endclk=rdtsc();
nclks=encdclk-stclk;
此时您可能会认为您已经确定了时钟频率,但即使它看起来正确,仍然可以进行改进。
所有的PC都包含一个PIT(可编程间隔计时器)设备,其中包含用于串口和系统时钟的计数器。它被馈入了频率为1193182 Hz的信号。系统时钟计数器被设置为最高倒计时值(65536),导致系统时钟的滴答频率为1193182/65536 => 18.2065 Hz或每54.925毫秒一次。
时钟增加到下一秒所需的滴答数将因此而异。通常需要18个滴答,有时需要19个滴答。这可以通过执行算法(上述)两次并存储结果来处理。两个结果将等同于两个18滴答序列或一个18和一个19。不会出现两个连续的19。因此,通过取两个结果中较小的那个,您将获得一个18滴答的秒钟。通过乘以18.2065并除以18.0来调整此结果,或者使用整数算术,乘以182065,加90000并除以180000。90000是180000的一半,用于四舍五入。如果您选择使用整数路线进行计算,请确保使用64位乘法和除法。
现在您将拥有一个以Hz为单位的CPU时钟速度x,可以转换为kHz ((x+500)/1000)或MHz ((x+5000000)/1000000)。500和500000分别是1000和1000000的一半,用于四舍五入。计算MHz时不要通过kHz值,因为可能会出现舍入问题。使用Hz值和第二个算法。
> 7z b
7-Zip 9.38 beta Copyright (c) 1999-2014 Igor Pavlov 2015-01-03
CPU Freq: 4266 4000 4266 4000 2723 4129 3261 3644 3362
#define YY1 sum += val; sum ^= val;
#define YY3 YY1 YY1 YY1 YY1
#define YY5 YY3 YY3 YY3 YY3
#define YY7 YY5 YY5 YY5 YY5
static const UInt32 kNumFreqCommands = 128;
EXTERN_C_BEGIN
static UInt32 CountCpuFreq(UInt32 sum, UInt32 num, UInt32 val)
{
for (UInt32 i = 0; i < num; i++)
{
YY7
}
return sum;
}
EXTERN_C_END
CPU_freq = tsc_freq * (aperf_t1 - aperf_t0) / (mperf_t1 - mperf_t0)
TSC(时间戳计数器)可以使用专用的x86指令从用户空间读取,但其频率必须通过针对时钟的校准来确定。最好的方法是从内核获取TSC频率(已完成校准)。
aperf和mperf计数器是模型特定寄存器MSRs,需要root权限才能访问。同样,有专用的x86指令用于访问MSRs。
由于mperf计数器速率与TSC速率成正比,而aperf速率与CPU频率成正比,因此您可以使用上述方程式获得CPU频率。
当然,如果CPU频率在t0-t1
时间差中发生变化(例如由于频率缩放),则可以使用此方法获得平均CPU频率。
我编写了一个小实用程序cpufreq,可用于测试此方法。
另请参阅:
这也是像 BogoMIPS 这样的东西的初衷,但是现在的 CPU 更加复杂了。 超标量 CPU 可以每个时钟周期发出多条指令,基于计算时钟周期来执行一段指令块的任何测量都非常不准确。
CPU 频率也根据负载和/或温度的提供而变化。 CPU 当前运行在 800 MHz 并不意味着它将始终运行在 800 MHz,它可能会根据需要进行缩小或扩大。
如果您确实需要知道时钟频率,则应将其作为参数传递。 板上的 EEPROM 将提供基础频率,如果时钟可以变化,您需要能够读取 CPU 的电源状态寄存器(或进行 OS 调用)来查找该瞬间的频率。
话虽如此,还有其他方法可以实现您要尝试的功能。 例如,如果您想对一个特定的代码路径进行高精度测量,CPU 可能正在运行固定频率的性能计数器,这是比读取滴答计数寄存器更好的墙钟时间测量。
我不确定您为什么需要汇编语言。 如果您使用的机器具有/proc文件系统,则可以运行以下命令:
> cat /proc/cpuinfo
可能会给你所需的东西。