【问题标题】:Need some help in getting the CPU Frequency在获取 CPU 频率方面需要一些帮助
【发布时间】:2013-07-20 09:26:50
【问题描述】:

我正在尝试制作一个 C# 软件来读取有关 CPU 的信息并将其显示给用户(就像 CPU-Z 一样)。 我目前的问题是找不到显示CPU频率的方法。

起初我尝试了使用 Win32_Processor 类的简单方法。事实证明它非常有效,除非 CPU 超频(或超频)。

然后,我发现我的注册表在 HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0 包含 CPU 的“标准”时钟(即使超频)。问题在于,在现代 CPU 中,当 CPU 不需要其全功率时,Core Multiplier 会降低,因此 CPU 频率也在变化,但注册表中的值保持不变。

我的下一步是尝试使用 RdTSC 来实际计算 CPU 频率。我为此使用了 C++,因为如果该方法有效,我可以将它嵌入到 C# 项目中。我在http://www.codeproject.com/Articles/7340/Get-the-Processor-Speed-in-two-simple-ways 找到了下一个代码 但问题是一样的:程序只给了我最大频率(就像在注册表值中,1-2 Mhz 的差异),而且它看起来也比它应该加载的 CPU 更多(我什至有 CPU 负载峰值)。

#include "stdafx.h"
#include <windows.h>
#include <cstdlib>
#include "intrin.h"
#include <WinError.h>
#include <winnt.h>

float ProcSpeedCalc() {

#define RdTSC __asm _emit 0x0f __asm _emit 0x31

    // variables for the clock-cycles:
    __int64 cyclesStart = 0, cyclesStop = 0;
    // variables for the High-Res Preformance Counter:
    unsigned __int64 nCtr = 0, nFreq = 0, nCtrStop = 0;

    // retrieve performance-counter frequency per second:
    if(!QueryPerformanceFrequency((LARGE_INTEGER *) &nFreq))
        return 0;

    // retrieve the current value of the performance counter:
    QueryPerformanceCounter((LARGE_INTEGER *) &nCtrStop);

    // add the frequency to the counter-value:
    nCtrStop += nFreq;


    _asm
    {// retrieve the clock-cycles for the start value:
        RdTSC
        mov DWORD PTR cyclesStart, eax
        mov DWORD PTR [cyclesStart + 4], edx
    }

    do{
    // retrieve the value of the performance counter
    // until 1 sec has gone by:
         QueryPerformanceCounter((LARGE_INTEGER *) &nCtr);
      }while (nCtr < nCtrStop);

    _asm
    {// retrieve again the clock-cycles after 1 sec. has gone by:
        RdTSC
        mov DWORD PTR cyclesStop, eax
        mov DWORD PTR [cyclesStop + 4], edx
    }

    // stop-start is speed in Hz divided by 1,000,000 is speed in MHz
    return    ((float)cyclesStop-(float)cyclesStart) / 1000000;
}


int _tmain(int argc, _TCHAR* argv[])
{

    while(true)
    {
        printf("CPU frequency = %f\n",ProcSpeedCalc());
        Sleep(1000);
    }

    return 0;
}

我还应该提到,我已经在 AMD CPU 上测试了最后一种方法。 我还为 RdTSC 方法尝试了一些其他代码,但没有一个能正常工作。

最后,我试图理解用于制作这个程序的代码https://code.google.com/p/open-hardware-monitor/source/browse/,但它对我来说太复杂了。

所以,我的问题是:如何使用 C++ 或 C# 实时确定 CPU 频率(即使 CPU 超频)?我知道这个问题被问了很多次,但没有人真正回答我的问题。

【问题讨论】:

  • 海森堡在 87 年前就预言,你无法做到这一点。只需确保您不需要此问题的答案即可。
  • @Hans:我确信所需的精度不会给 Heisenberg 带来问题。
  • 我给出了一个答案,它展示了如何使用 C/C++ 做到这一点,即使对于超频的系统也是如此。

标签: c# c++ visual-c++


【解决方案1】:

是的,该代码一直处于忙碌状态,等待整整一秒,这导致该核心在一秒钟内处于 100% 忙碌状态。对于动态时钟算法来说,一秒钟的时间足以检测负载并将 CPU 频率提高到节能状态。如果带 boost 的处理器实际显示的频率高于标记频率,我不会感到惊讶。

不过,这个概念还不错。您需要做的是睡眠 大约一秒钟的时间间隔。然后,不要假设 RDTSC 调用恰好相隔一秒,而是除以 QueryPerformanceCounter 指示的实际时间。

另外,我建议在QueryPerformanceCounter 调用之前和之后检查RDTSC,以检测RDTSCQueryPerformanceCounter 之间是否存在上下文切换,这会弄乱您的结果。


不幸的是,新处理器上的RDTSC 实际上并没有计算 CPU 时钟周期。所以这并不能反映动态变化的 CPU 时钟速率(不过,它确实测量了没有忙等待的标称速率,因此它比问题中提供的代码有了很大的改进)。

所以看起来您毕竟需要访问特定于模型的寄存器。其中can't be done from user-modeThe OpenHardwareMonitor project既有可以使用的驱动,又有code for the frequency calculations


float ProcSpeedCalc()
{
    /*
        RdTSC:
          It's the Pentium instruction "ReaD Time Stamp Counter". It measures the
          number of clock cycles that have passed since the processor was reset, as a
          64-bit number. That's what the <CODE>_emit</CODE> lines do.
    */
    // Microsoft inline assembler knows the rdtsc instruction.  No need for emit.

    // variables for the CPU cycle counter (unknown rate):
    __int64 tscBefore, tscAfter, tscCheck;
    // variables for the Performance Counter 9steady known rate):
    LARGE_INTEGER hpetFreq, hpetBefore, hpetAfter;


    // retrieve performance-counter frequency per second:
    if (!QueryPerformanceFrequency(&hpetFreq)) return 0;

    int retryLimit = 10;    
    do {
        // read CPU cycle count
        _asm
        {
            rdtsc
            mov DWORD PTR tscBefore, eax
            mov DWORD PTR [tscBefore + 4], edx
        }

        // retrieve the current value of the performance counter:
        QueryPerformanceCounter(&hpetBefore);

        // read CPU cycle count again, to detect context switch
        _asm
        {
            rdtsc
            mov DWORD PTR tscCheck, eax
            mov DWORD PTR [tscCheck + 4], edx
        }
    } while ((tscCheck - tscBefore) > 800 && (--retryLimit) > 0);

    Sleep(1000);

    do {
        // read CPU cycle count
        _asm
        {
            rdtsc
            mov DWORD PTR tscAfter, eax
            mov DWORD PTR [tscAfter + 4], edx
        }

        // retrieve the current value of the performance counter:
        QueryPerformanceCounter(&hpetAfter);

        // read CPU cycle count again, to detect context switch
        _asm
        {
            rdtsc
            mov DWORD PTR tscCheck, eax
            mov DWORD PTR [tscCheck + 4], edx
        }
    } while ((tscCheck - tscAfter) > 800 && (--retryLimit) > 0);

    // stop-start is speed in Hz divided by 1,000,000 is speed in MHz
    return (double)(tscAfter - tscBefore) / (double)(hpetAfter.QuadPart - hpetBefore.QuadPart) * (double)hpetFreq.QuadPart / 1.0e6;
}

大多数编译器都提供 __rdtsc() 内在函数,在这种情况下,您可以使用 tscBefore = __rdtsc(); 而不是 __asm 块。不幸的是,这两种方法都是特定于平台和编译器的。

【讨论】:

  • 你能帮我一下吗?代码不是我的,我也不知道 QueryPerformanceCounter 是如何工作的。
  • @TheQuestioner:您的计算机有一个高精度事件计时器,称为 HPET。它的频率低于 CPU 频率,但不会改变以节省电力(HPET 可能会停止实际的睡眠模式,但我们只是担心 Cool'n'Quiet / SpeedStep / TurboBoost 效果,对吗?)。 QueryPerformanceCounter 从 HPET 读取计数,QueryPerformanceFrequency 告诉您它的频率。
  • 所以,你找出 CPU 频率和 HPET 频率的比值,然后乘以 HPET 频率。或者,使用 HPET 计算经过的时间,并使用它来计算 CPU 频率。不止一种方式来看待同一个问题。
  • 好的,我现在明白了。但是我仍然没有得到我应该修改的内容(对不起,但我真的是这方面的初学者)。你能把修改后的代码发给我吗?
  • 第 1 步:将 do-while 循环替换为对 Sleep 的调用。 Step 2. 最后再拨打QueryPerformanceCounter。步骤 3. 将 1000000 替换为实际经过的时间,由 QueryPerformanceCounter 结果确定。
【解决方案2】:

答案取决于你真正想知道什么。

如果您的目标是找到您当前正在运行的某个特定应用程序的运行频率,那么这是一个难题,需要管理员/root 权限才能访问特定型号的寄存器,甚至可能访问 BIOS。您可以在 Windows 上使用 CPU-Z 或在 Linux 上使用 powertop。

但是,如果您只是想知道处理器在负载下的一个或多个线程的运行频率,以便您可以计算峰值触发器(这就是我关心这个的原因),那么这可以通过更多或不需要管理员权限的不太通用的代码。

我从 Bruce Dawson 在http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/ 的代码中得到了这个想法。我主要扩展了他的代码以使用 OpenMP 处理多个线程。

我已经在 Linux 和 Windows 上的英特尔处理器上进行了测试,包括 Nahalem、Ivy Bridge 和 Haswell,一个插槽最多四个插槽(40 个线程)。结果与正确答案的偏差均小于 0.5%。

我在这里how-can-i-programmatically-find-the-cpu-frequency-with-c描述了如何确定频率,所以我不会重复所有细节。

【讨论】:

    【解决方案3】:

    您的问题根本无法回答。 CPU频率不断变化。有时操作系统知道这些变化并且可以告诉你,但有时它不知道。 CPU 可能会超频(TurboBoost)或超频(由于过热)。一些处理器通过以相同的速率运行时钟但只在某些周期上工作来降低功耗以避免熔化,此时时钟频率的整个概念是没有意义的。

    在这篇文章中,我讨论了大量机器,我分析了 CPU 在没有 Windows 注意到的情况下受到热限制。

    http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/

    可以编写一些特定于处理器的杂乱代码来检测这一点,但它需要管理员权限。

    我的意思是,您问的是一个无法回答的问题,而且在大多数情况下,这不是您应该问的问题。使用注册表中的值,或询问 Windows CPU 运行的频率是多少(请参阅 PROCESSOR_POWER_INFORMATION)并调用它足够好。

    【讨论】:

    • 显示与 CPU-Z 相同的数据不能“根本无法回答”。 CPU-Z 证明了它是可能的。你的整篇博客文章都证明它是有用的。
    • 当您得到答案时,它已经过时了。这是一个问题。而且,在许多 Intel CPU 上,热节流是通过更改占空比来处理的,因此在大多数滴答声中什么都不做。也就是说,CPU-Z 可能会显示您的 CPU 以 2.0 GHz 运行,但如果它过热,则占空比可能只有 0.5 GHz。 CPU-Z 确实显示了一个数字,但它并不总是有意义的。您可以监控频率和占空比以获得答案,但在某些时候它变得有点疯狂。我通过完全依赖的整数相加来测量频率,效果很好,但是很糟糕。
    • 是的,我想知道的是那个 CPU-Z 无意义的数字。还有很多程序,比如 CPU-Z 可以显示频率,所以我的问题不是“根本无法回答”。但无论如何,我差不多一年前就放弃了这个项目。
    • +1,虽然你的答案是错误的,但你的链接(特别是你提供的代码)给了我如何解决这个问题的想法。这个问题是可以回答的。虽然这取决于问题的提出方式。要计算应用程序的频率,需要管理员权限和计数器。但是要计算一个线程或多个线程的 CPU 频率,可以使用基于在多个内核上运行的 SpinALot 函数的代码来完成。我在 2 核 Haswell、4 核、ivy bridge 和 40 核 (80 HT) Nahalem NUMA 系统上对此进行了测试,并得到了所有这些的正确答案。
    • 酷。很高兴这有效。我可能应该以不同的方式表达我的答案,因为我还发现测量 CPU 频率很有用(并将其与 Windows 认为 CPU 运行的速度进行比较)。请参阅我添加到 CS:GO 中的 cpu_frequency_monitoring 命令:blog.counter-strike.net/index.php/2014/07/9942 监控 CPU 频率的程序通常会被误导,但偶尔会很出色。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    • 2015-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多