37

CPUz 之类的程序非常擅长提供有关系统的深度信息(总线速度、内存时序等)

但是,是否有一种编程方式来计算每个内核(以及每个处理器,在每个 CPU 具有多个内核的多处理器系统中)频率,而无需处理 CPU 特定信息。

我正在尝试开发一种反作弊工具(用于时钟受限的基准竞赛),它能够在系统中所有活动内核(跨所有处理器)的基准运行期间记录 CPU 时钟。

4

6 回答 6

36

我将在这里扩展我的评论。这对我来说太大而深入,无法放入评论中。

您尝试做的事情非常困难-由于以下原因而变得不切实际:

  • 没有可移植的方式来获得处理器频率。由于 SpeedStep 和 Turbo Boost 等影响,rdtsc并不总是给出正确的频率。
  • 所有已知的测量频率的方法都需要准确的时间测量。然而,一个确定的作弊者可以篡改系统中的所有时钟和计时器。
  • 以防篡改的方式准确读取处理器频率和时间将需要内核级访问。这意味着 Windows 的驱动程序签名。

没有可移植的方式来获取处理器频率:

获得 CPU 频率的“简单”方法是调用rdtsc两次,其间有固定的持续时间。然后除以差异将为您提供频率。

问题是rdtsc没有给出处理器的真实频率。因为游戏等实时应用依赖它,rdtsc需要通过CPU节流和Turbo Boost保持一致。因此,一旦您的系统启动,rdtsc将始终以相同的速率运行(除非您开始使用SetFSB或其他东西弄乱总线速度)。

例如,在我的 Core i7 2600K 上,rdtsc将始终显示频率为3.4 GHz. 但实际上,它通过超频的 Turbo Boost 倍频器1.6 GHz在.4.6 GHz46x

但是一旦你找到了测量真实频率的方法,(或者你对 足够满意),你可以使用thread-affinitiesrdtsc轻松获得每个内核的频率。

获取真实频率:

要获得处理器的真实频率,您需要访问 MSR(特定于型号的寄存器)或硬件性能计数器。

这些是内核级指令,因此需要使用驱动程序。如果您出于分发目的在 Windows 中尝试此操作,则需要通过正确的驱动程序签名协议。此外,代码会因处理器品牌和型号而异,因此您需要为每一代处理器提供不同的检测代码。

一旦你到了这个阶段,就有多种方法可以读取频率。

在 Intel 处理器上,硬件计数器可让您计算原始 CPU 周期。结合精确测量实时的方法(下一节),您可以计算真实频率。MSR 使您可以访问其他信息,例如 CPU 频率倍数。


所有已知的频率测量方法都需要准确测量时间:

这也许是更大的问题。您需要一个计时器才能测量频率。有能力的黑客将能够篡改您可以在 C/C++ 中使用的所有时钟。这包括以下所有内容:

  • clock()
  • gettimeofday()
  • QueryPerformanceCounter()
  • ETC...

名单还在继续。换句话说,您不能信任任何计时器,因为有能力的黑客将能够欺骗所有计时器。例如clock()gettimeofday()可以通过直接在操作系统中更改系统时钟来愚弄。愚弄QueryPerformanceCounter()更难。

获得时间的真实测量:

上面列出的所有时钟都是易受攻击的,因为它们通常以某种方式从同一个系统基本时钟派生而来。并且该系统基本时钟通常与系统基本时钟相关联 - 可以在系统已经启动后通过超频实用程序进行更改。

因此,获得可靠且防篡改的时间测量的唯一方法是读取外部时钟,例如HPETACPI。不幸的是,这些似乎也需要内核级访问。


总结:

几乎可以肯定,构建任何类型的防篡改基准测试都需要编写需要为 Windows 进行证书签名的内核模式驱动程序。对于临时基准编写者来说,这通常是一个太大的负担。

这导致了防篡改基准的短缺,这可能导致近年来竞争激烈的超频社区整体下滑。

于 2011-12-02T07:32:23.290 回答
3

我意识到这已经得到了回答。我也意识到这基本上是一种黑色艺术,所以请接受或离开它 - 或提供反馈。

为了找到节流(感谢 microsft、hp 和戴尔)HyperV 主机(不可靠的性能计数器)和 HyperV 来宾(只能获得库存 CPU 速度,而不是当前)的时钟频率,我已经通过试验错误和侥幸,创建一个循环,每个时钟只循环一次。

代码如下 - C# 5.0, SharpDev, 32bit, Target 3.5, Optimize on (crucial), no debuger active (crucial)

        long frequency, start, stop;
        double multiplier = 1000 * 1000 * 1000;//nano
        if (Win32.QueryPerformanceFrequency(out frequency) == false)
            throw new Win32Exception();

        Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
        const int gigahertz= 1000*1000*1000;
        const int known_instructions_per_loop = 1; 

        int iterations = int.MaxValue;
        int g = 0;

        Win32.QueryPerformanceCounter(out start);
        for( i = 0; i < iterations; i++)
        {
            g++;
            g++;
            g++;
            g++;
        }
        Win32.QueryPerformanceCounter(out stop);

        //normal ticks differs from the WMI data, i.e 3125, when WMI 3201, and CPUZ 3199
        var normal_ticks_per_second = frequency * 1000;
        var ticks = (double)(stop - start);
        var time = (ticks * multiplier) /frequency;
        var loops_per_sec = iterations / (time/multiplier);
        var instructions_per_loop = normal_ticks_per_second  / loops_per_sec;

        var ratio = (instructions_per_loop / known_instructions_per_loop);
        var actual_freq = normal_ticks_per_second / ratio;

        Console.WriteLine( String.Format("Perf counhter freq: {0:n}", normal_ticks_per_second));
        Console.WriteLine( String.Format("Loops per sec:      {0:n}", loops_per_sec));
        Console.WriteLine( String.Format("Perf counter freq div loops per sec: {0:n}", instructions_per_loop));
        Console.WriteLine( String.Format("Presumed freq: {0:n}", actual_freq));
        Console.WriteLine( String.Format("ratio: {0:n}", ratio));

笔记

  • 如果调试器处于活动状态,则每个循环 25 条指令
  • 考虑在手前运行 2 或 3 秒循环以启动处理器(或至少尝试启动,知道这些天服务器受到的限制有多大)
  • 在 64 位 Core2 和 Haswell Pentium 上进行测试,并与 CPU-Z 进行比较
于 2015-08-08T03:17:51.040 回答
2

我之前已经发布过这个主题(连同一个基本算法):这里。据我所知,算法(见讨论)非常准确。例如,Windows 7 报告我的 CPU 时钟为 2.00 GHz,CPU-Z 报告为 1994-1996 MHz,我的算法报告为 1995025-1995075 kHz。

该算法执行大量循环来执行此操作,这会导致 CPU 频率增加到最大值(在基准测试期间也会如此),因此速度限制软件不会发挥作用。

此处此处的附加信息。

关于速度限制的问题,我真的不认为这是一个问题,除非应用程序使用速度值来确定经过的时间并且时间本身非常重要。例如,如果一个除法需要 x 个时钟周期来完成,那么 CPU 运行在 3 GHz 还是 300 MHz 无关紧要:它仍然需要 x 个时钟周期,唯一的区别是它将在十分之一内完成除法在@ 3 GHz 的时间。

于 2011-12-13T10:58:21.387 回答
2

最简单的方法之一是使用RDTSC,但鉴于这是用于反作弊机制,我会将其作为内核驱动程序或虚拟机管理程序驻留代码段。

您可能还需要滚动您自己的时序代码**,这又可以通过RDTSC(QPC 如下例中使用的使用) 来完成RDTSC,实际上逆向工程和使用本地副本非常简单,这意味着篡改它,你需要篡改你的驱动程序)。

void GetProcessorSpeed()
{
    CPUInfo* pInfo = this;
    LARGE_INTEGER qwWait, qwStart, qwCurrent;
    QueryPerformanceCounter(&qwStart);
    QueryPerformanceFrequency(&qwWait);
    qwWait.QuadPart >>= 5;
    unsigned __int64 Start = __rdtsc();
    do
    {
        QueryPerformanceCounter(&qwCurrent);
    }while(qwCurrent.QuadPart - qwStart.QuadPart < qwWait.QuadPart);
    pInfo->dCPUSpeedMHz = ((__rdtsc() - Start) << 5) / 1000000.0;
}

** 我这将是@Mystical 提到的安全性,但由于我从未感受到颠覆低级系统计时机制的冲动,因此可能涉及更多,如果 Mystical 可以在其上添加一些东西会很好:)

于 2011-12-02T07:23:58.237 回答
1

您需要使用CallNtPowerInformation。这是来自putil项目的代码示例。有了这个,您可以获得当前和最大 CPU 频率。据我所知,不可能获得每个 CPU 的频率。

于 2017-01-20T17:39:36.550 回答
1

请参阅此白皮书:基于英特尔® 酷睿™ 微架构 (Nehalem) 的处理器中的英特尔® Turbo Boost 技术。基本上,在采样周期 T 内产生 UCC 固定性能计数器的多次读取。

Relative.Freq = Delta(UCC)  / T

Where:
   Delta() = UCC @ period T
                 - UCC @ period T-1

从 Nehalem 架构开始,UCC 相对于内核的 Unhalted 状态增加和减少点击滴答的数量。

当 SpeedStep 或 Turbo Boost 被激活时,使用 UCC 的估计频率将被相应地测量;而 TSC 保持不变。例如,Turbo Boost 的作用表明 Delta(UCC) 大于或等于 Delta(TSC)

Cyring的函数Core_Cycle函数示例 | CoreFreq GitHub。

于 2016-10-10T04:48:24.367 回答