汇编CPU频率测量算法

18

常用于测量处理器频率的算法有哪些?

10个回答

20

Core Duo之后的Intel CPU支持两个名为IA32_MPERF和IA32_APERF的模型特定寄存器。


MPERF计数最大频率,而APERF计数实际当前频率。

实际频率由以下公式给出:

freq = max_frequency * APERF / MPERF

您可以使用此流程读取它们。

; 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运行。

我不知道操作系统是否提供了读取这些寄存器的接口,虽然它们主要用于功耗管理,因此可能不提供这样的接口。


3
我使用这个网站将LaTeX代码转换成GIF图像:http://en.wikibooks.org/wiki/LaTeX/Mathematics。关于使用LaTeX排版数学公式的语法可在这里找到:http://en.wikibooks.org/wiki/LaTeX/Mathematics。 - Bastien Léonard
1
@NathanFellman Nathan,但是你如何获得最大频率? - Alexandru
@NathanFellman 我想就我对最大频率的评论/问题提出两点。第一,从内核驱动程序中,您不能只使用QueryPerformanceFrequency来获取此值(无法允许您使用Windows.h标头,您需要使用特殊的内核标头,而没有访问此方法)。您可能可以将MSR值传递给用户模式应用程序并像这样计算它,但是...第二点,即使您可以使用它,它也不会给出亲和线程的频率,而只是整个CPU的频率,如果您想要知道一个核心的频率,这很糟糕。 - Alexandru
@Alexandru,抱歉这么晚才回复你。关于你的第一个问题,关于最大频率,你可以使用CPUID.0x16来获取最大非Turbo频率。我不确定当你提出问题时是否存在这个叶子,但现在确实存在了。关于你的第二个问题,这些MSR是逻辑处理器范围的,因此每个单独的线程都有自己的计数器集,因此实际上这种方法将为您提供单个线程的平均频率。 - Nathan Fellman

7
我将在这个答案中提供一些细节,但是没关系。多年前我曾在基于Windows的PC上解决过这个问题,所以我处理的是像486、Pentium等Intel x86系列处理器。在那种情况下,标准算法是执行一系列长的DIVide指令,因为它们通常是Intel指令集中最CPU密集的单个指令。因此,内存预取和其他架构问题对指令执行时间没有实质影响——预取队列始终是满的,并且指令本身不会触及任何其他内存。
你需要使用你所运行环境中可以访问的最高分辨率时钟来计时。(在我的情况下,我在类似启动时运行的PC兼容机上,所以我直接编程主板上的定时器芯片。通常不建议在真正的操作系统中这样做,通常有一些适当的API可以调用)。
你需要处理的主要问题是不同的CPU类型。当时有Intel、AMD和一些小厂商如Cyrix制造x86处理器。每种型号都有其与该DIV指令相关的性能特征。我的汇编计时函数只会返回由一定数量的DIV指令在紧密循环中完成所需的时钟周期数。
因此,我所做的是从运行我想要计时的每个处理器型号的实际PC上收集一些时间(从该函数中返回的原始返回值),并将其记录在一个电子表格中,与已知的处理器速度和处理器类型相对应。我实际上有一个命令行工具,它只是我的计时函数周围的一个薄壳,我会把磁盘带到电脑商店,从展示模型上获取计时! (当时我为一家非常小的公司工作)。
使用这些原始计时,我可以绘制出任何已知速度的特定CPU的理论图表。
这里是诀窍:我总是讨厌运行一个实用程序,它会宣布你的CPU是99.8 Mhz或其他什么。显然它是100 Mhz,只是测量中有一个小的舍入误差。在我的电子表格中,我记录了每个处理器供应商销售的实际速度。然后,我会使用实际计时的图形来估计任何已知速度的预计计时。但我会建立一个沿着线的点的表格,其中计时应该四舍五入到下一个速度。
换句话说,如果100个滴答声完成所有重复的除法意味着500 Mhz,而200个滴答声意味着250 Mhz,那么我会建立一个表格,说任何低于150的东西都是500 Mhz,而任何高于那个速度的东西都是250 Mhz。(假设这是该芯片供应商提供的唯一两种速度)。这很好,因为即使PC上的某些奇怪软件扰乱了我的计时,最终结果通常仍然非常准确。
当然,现在,在超频、动态时钟速度管理和其他类似的技巧流行的时代,这样的方案会不太实用。至少你需要做一些事情来确保CPU在运行计时函数之前处于其最高的动态选择速度。

好的,我现在会回去赶孩子离开我的草坪了。


2
我认为99.8的速度可能是准确的,他们本来瞄准100MHz但没达到。你不希望他们像手表一样淘汰那些时间上有点偏差的系统芯片。 - Arthur Kalliokoski
1
我同意。你不仅需要目标理论,还要考虑到计算机中的波动因素。主板实际上会接收到波动的电压。一个“100 MHz”的CPU实际上可能是一个带有4倍乘数器的25 MHz CPU或者是一个带有3倍乘数器的33 MHz CPU。 - Joe Plante

4

在英特尔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处于最高可用频率状态。


RDTSC在现代CPU上计算的是“参考周期”,而不是核心时钟周期。因此,这将确定大多数现代CPU的“额定”/最大持续时钟频率,而不是当前时钟频率。除非在永久运行于不同速度的超频系统上,否则这可能是您实际想要的。 - Peter Cordes

2
我使用以下(伪)算法:

我使用以下(伪)算法:

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值和第二个算法。


2
一种选择是通过运行已知每循环指令的代码来感知CPU频率。此功能包含在7zip中,自v9.20左右开始提供。
> 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

最终数字应该是正确的(在我的电脑和许多其他人的电脑上,我发现它非常正确——测试运行非常快,因此可能不会触发Turbo,而设置为平衡/节能模式的服务器很可能给出约1GHz的读数)。
源代码位于GitHub(官方源是从7-zip.org下载)。
其中最重要的部分是:
#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

2
在英特尔CPU上,获取当前(平均)CPU频率的常见方法是从几个CPU计数器中计算得出:
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,可用于测试此方法。

另请参阅:


谢谢,这很有帮助。还可以参考https://patchwork.kernel.org/project/linux-pm/patch/52f711be59539723358bea1aa3c368910a68b46d.1459485198.git.len.brown@intel.com/,他们在那里提到,对于瞬时频率,您可以读取`MSR_PERF_STATUS`,但这需要操作系统启用频率控制并实际操作该值。 - 1110101001
1
此外,请参阅英特尔手册 https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-484.html"默认情况下,IA32_MPERF计数器在强制空闲期间计数,就好像逻辑处理器处于活动状态。IA32_APERF计数器在强制空闲状态下不计数。这种计数约定允许操作系统通过 ΔACNT/ΔMCNT * TSC 频率 来计算最后一个 MWAIT 退出和下一个 MWAIT 进入(OS 可见 C0)之间逻辑处理器的平均有效频率。" - 1110101001
1
另请参阅英特尔手册 https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-484.html“默认情况下,IA32_MPERF计数器在强制空闲期间计数,就好像逻辑处理器是活动的。IA32_APERF计数器在强制空闲状态下不计数。这种计数约定允许操作系统通过 ΔACNT/ΔMCNT * TSC频率 计算出最后一个MWAIT退出和下一个MWAIT进入(OS可见的C0)之间逻辑处理器的平均有效频率。” - undefined
最后,关于为什么有些答案提到名义频率(最大值是误导性的,因为你想使用名义即非增压最大值)而不是TSC频率,根据英特尔的说法,“该速率可能由处理器的最大核心时钟与总线时钟比率设置,也可能由处理器引导时的最大解析频率设置。最大解析频率可能与处理器基础频率不同,请参阅第18.7.2节了解更多详细信息。在某些处理器上,TSC频率可能与频率品牌字符串不同。” - 1110101001
1
@1110101001 感谢附加的参考资料 - 如果你觉得这个答案有帮助,也可以给它点赞... :) - maxschlepzig
显示剩余2条评论

1
“lmbench” 提供了一种可移植的 CPU 频率算法,适用于不同的架构。
它运行一些不同的循环,处理器的时钟速度是各个循环执行频率的最大公约数。
当我们能够获得循环计数相对质数时,这种方法应该总是有效的。

http://www.bitmover.com/lmbench/


1

这也是像 BogoMIPS 这样的东西的初衷,但是现在的 CPU 更加复杂了。 超标量 CPU 可以每个时钟周期发出多条指令,基于计算时钟周期来执行一段指令块的任何测量都非常不准确。

CPU 频率也根据负载和/或温度的提供而变化。 CPU 当前运行在 800 MHz 并不意味着它将始终运行在 800 MHz,它可能会根据需要进行缩小或扩大。

如果您确实需要知道时钟频率,则应将其作为参数传递。 板上的 EEPROM 将提供基础频率,如果时钟可以变化,您需要能够读取 CPU 的电源状态寄存器(或进行 OS 调用)来查找该瞬间的频率。

话虽如此,还有其他方法可以实现您要尝试的功能。 例如,如果您想对一个特定的代码路径进行高精度测量,CPU 可能正在运行固定频率的性能计数器,这是比读取滴答计数寄存器更好的墙钟时间测量。


0
快速谷歌搜索 AMDIntel 显示 CPUID 应该给您访问 CPU 的最大频率。

1
我认为它只会识别处理器型号。 - Bastien Léonard

0

我不确定您为什么需要汇编语言。 如果您使用的机器具有/proc文件系统,则可以运行以下命令:

> cat /proc/cpuinfo

可能会给你所需的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接