如何计算CPU内核的频率

How to calculate the frequency of CPU cores

本文关键字:CPU 内核 频率 计算 何计算      更新时间:2023-10-16

我正在尝试使用RDTSC,但似乎我的方法可能错误,无法获得核心速度:

#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
struct Core
{
    int CoreNumber;
};
static void startMonitoringCoreSpeeds(void *param)
{
    Core core = *((Core *)param);
    SetThreadAffinityMask(GetCurrentThread(), 1 << core.CoreNumber);
    while (true)
    {
        DWORD64 first = __rdtsc();
        Sleep(1000);
        DWORD64 second = __rdtsc();
        cout << "Core " << core.CoreNumber << " has frequency " << ((second - first)*pow(10, -6)) << " MHz" << endl;
    }
}
int GetNumberOfProcessorCores()
{
    DWORD process, system;
    if (GetProcessAffinityMask(GetCurrentProcess(), &process, &system))
    {
        int count = 0;
        for (int i = 0; i < 32; i++)
        {
            if (system & (1 << i))
            {
                count++;
            }
        }
        return count;
    }
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    return sysinfo.dwNumberOfProcessors;
}
int _tmain(int argc, _TCHAR* argv[])
{
    for (int i = 0; i < GetNumberOfProcessorCores(); i++)
    {
        Core *core = new Core {0};
        core->CoreNumber = i;
        _beginthread(startMonitoringCoreSpeeds, 0, core);
    }
    cin.get();
}

它总是打印出3.3 GHz左右的值,这是错误的,因为像Turbo Boost这样的东西不时打开,我的核心肯定会跳到4.3 GHz。让我对照一下这一想法背后的一些文章。

首先(http://users.utcluj.ro/~ancapop/labscs/SCS2.pdf):"处理器核心上的TSC没有同步。因此,不确定进程是否在从一个核心到另一个核心的执行,测量不会受到影响。为了避免这个问题,必须将测量的进程的亲和性设置为仅一个核心,以防止进程迁移。"这告诉我,RDTSC应该使用我设置的亲和掩码为线程上的每个内核返回不同的值,这很好

其次,请查看这篇文章(http://randomascii.wordpress.com/2011/07/29/rdtsc-in-the-age-of-sandybridge/):"如果你需要一个能在核心之间工作并能用来测量时间的一致性计时器,那么这是个好消息。如果你想测量实际的CPU时钟周期,那么你就太倒霉了。如果你想要在各种CPU系列之间保持一致性,那么你自己就太糟糕了。更新:《英特尔系统编程指南》第16.11节记录了时间戳计数器的这种行为t表示,在较旧的处理器上,时钟速率会发生变化,但在较新的处理器上保持不变。它最后说,对于Constant TSC,"这是向前发展的架构行为。"好吧,这告诉我RDTSC保持一致,这使得我的上述结果有意义,因为我的CPU内核额定为标准3.3 GHz…

这真的引出了一个问题,像英特尔的Turbo Boost技术监视器、Piriform的Speccy和CPUID的CPU-Z这样的应用程序如何在进行Turbo Boost时实时测量处理器的时钟速度

完整的解决方案如下。我已经对MSDN上的IOCTL示例驱动程序进行了调整。请注意,IOCTL样本是我能找到的唯一相对WDM样本骨架驱动程序,也是我能找到最接近WDM模板的东西,因为WDK中的大多数开箱即用的内核模式模板都是基于WDF的驱动程序(任何WDM驱动程序模板实际上都是空白的,完全没有源代码),但我看到的唯一用于输入/输出的样本逻辑是通过基于WDM的驱动程序。此外,我还学到了一些有趣的事实:内核驱动程序不喜欢浮点运算,你不能使用"windows.h",这确实限制了你使用"ntddk.h",一个特殊的内核模式头。这也意味着我不能在内核模式下进行所有计算,因为我不能在那里调用QueryPerformanceFrequency之类的函数,因此,我必须获得时间戳之间的平均性能比,并将它们返回到用户模式进行某些计算(如果没有QueryPerformanceFrequency,你从存储像QueryPerformanceCounter使用的滴答声的CPU寄存器中获得的值是无用的,因为你不知道步长;也许有解决方法,但我选择只使用平均值,因为它运行得非常好)。此外,根据1秒睡眠,我使用这一点的原因是,否则你几乎是在多个线程上旋转计算,这真的会打乱你的计算,因为你的频率会随着每个核心的频率而上升,不断检查QueryPerformanceCounter的结果(你做更多计算时会驱动你的核心)-更不用说-这是一个比率。。。所以delta时间并没有那么重要,因为它每次的周期您总是可以增加delta,它仍然应该为您提供与步长相同的比率。此外,这是我所能做到的最简约的。祝你好运,让它比这个小或短得多。此外,如果你想安装驱动程序,除非你想从第三方购买代码签名证书,否则你有两个选项,这两个选项都很糟糕,所以选择一个并接受它。让我们从驱动程序开始:

驱动程序.c

//
// Include files.
//
#include <ntddk.h>          // various NT definitions
#include <string.h>
#include <intrin.h>
#include "driver.h"
#define NT_DEVICE_NAME      L"\Device\KernelModeDriver"
#define DOS_DEVICE_NAME     L"\DosDevices\KernelModeDriver"
#if DBG
#define DRIVER_PRINT(_x_) 
                DbgPrint("KernelModeDriver.sys: ");
                DbgPrint _x_;
#else
#define DRIVER_PRINT(_x_)
#endif
//
// Device driver routine declarations.
//
DRIVER_INITIALIZE DriverEntry;
_Dispatch_type_(IRP_MJ_CREATE)
_Dispatch_type_(IRP_MJ_CLOSE)
DRIVER_DISPATCH DriverCreateClose;
_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
DRIVER_DISPATCH DriverDeviceControl;
DRIVER_UNLOAD DriverUnloadDriver;
VOID
PrintIrpInfo(
    PIRP Irp
    );
VOID
PrintChars(
    _In_reads_(CountChars) PCHAR BufferAddress,
    _In_ size_t CountChars
    );
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, DriverCreateClose)
#pragma alloc_text( PAGE, DriverDeviceControl)
#pragma alloc_text( PAGE, DriverUnloadDriver)
#pragma alloc_text( PAGE, PrintIrpInfo)
#pragma alloc_text( PAGE, PrintChars)
#endif // ALLOC_PRAGMA

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT   DriverObject,
    _In_ PUNICODE_STRING      RegistryPath
    )
/*++
Routine Description:
    This routine is called by the Operating System to initialize the driver.
    It creates the device object, fills in the dispatch entry points and
    completes the initialization.
Arguments:
    DriverObject - a pointer to the object that represents this device
    driver.
    RegistryPath - a pointer to our Services key in the registry.
Return Value:
    STATUS_SUCCESS if initialized; an error otherwise.
--*/
{
    NTSTATUS        ntStatus;
    UNICODE_STRING  ntUnicodeString;    // NT Device Name "DeviceKernelModeDriver"
    UNICODE_STRING  ntWin32NameString;    // Win32 Name "DosDevicesKernelModeDriver"
    PDEVICE_OBJECT  deviceObject = NULL;    // ptr to device object
    UNREFERENCED_PARAMETER(RegistryPath);
    RtlInitUnicodeString( &ntUnicodeString, NT_DEVICE_NAME );
    ntStatus = IoCreateDevice(
        DriverObject,                   // Our Driver Object
        0,                              // We don't use a device extension
        &ntUnicodeString,               // Device name "DeviceKernelModeDriver"
        FILE_DEVICE_UNKNOWN,            // Device type
        FILE_DEVICE_SECURE_OPEN,        // Device characteristics
        FALSE,                          // Not an exclusive device
        &deviceObject );                // Returned ptr to Device Object
    if ( !NT_SUCCESS( ntStatus ) )
    {
        DRIVER_PRINT(("Couldn't create the device objectn"));
        return ntStatus;
    }
    //
    // Initialize the driver object with this driver's entry points.
    //
    DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
    DriverObject->DriverUnload = DriverUnloadDriver;
    //
    // Initialize a Unicode String containing the Win32 name
    // for our device.
    //
    RtlInitUnicodeString( &ntWin32NameString, DOS_DEVICE_NAME );
    //
    // Create a symbolic link between our device name  and the Win32 name
    //
    ntStatus = IoCreateSymbolicLink(
                        &ntWin32NameString, &ntUnicodeString );
    if ( !NT_SUCCESS( ntStatus ) )
    {
        //
        // Delete everything that this routine has allocated.
        //
        DRIVER_PRINT(("Couldn't create symbolic linkn"));
        IoDeleteDevice( deviceObject );
    }

    return ntStatus;
}

NTSTATUS
DriverCreateClose(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
/*++
Routine Description:
    This routine is called by the I/O system when the KernelModeDriver is opened or
    closed.
    No action is performed other than completing the request successfully.
Arguments:
    DeviceObject - a pointer to the object that represents the device
    that I/O is to be done on.
    Irp - a pointer to the I/O Request Packet for this request.
Return Value:
    NT status code
--*/
{
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return STATUS_SUCCESS;
}
VOID
DriverUnloadDriver(
    _In_ PDRIVER_OBJECT DriverObject
    )
/*++
Routine Description:
    This routine is called by the I/O system to unload the driver.
    Any resources previously allocated must be freed.
Arguments:
    DriverObject - a pointer to the object that represents our driver.
Return Value:
    None
--*/
{
    PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject;
    UNICODE_STRING uniWin32NameString;
    PAGED_CODE();
    //
    // Create counted string version of our Win32 device name.
    //
    RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME );

    //
    // Delete the link from our device name to a name in the Win32 namespace.
    //
    IoDeleteSymbolicLink( &uniWin32NameString );
    if ( deviceObject != NULL )
    {
        IoDeleteDevice( deviceObject );
    }

}
NTSTATUS
DriverDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
/*++
Routine Description:
    This routine is called by the I/O system to perform a device I/O
    control function.
Arguments:
    DeviceObject - a pointer to the object that represents the device
        that I/O is to be done on.
    Irp - a pointer to the I/O Request Packet for this request.
Return Value:
    NT status code
--*/
{
    PIO_STACK_LOCATION  irpSp;// Pointer to current stack location
    NTSTATUS            ntStatus = STATUS_SUCCESS;// Assume success
    ULONG               inBufLength; // Input buffer length
    ULONG               outBufLength; // Output buffer length
    void                *inBuf; // pointer to input buffer
    unsigned __int64    *outBuf; // pointer to the output buffer
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
    irpSp = IoGetCurrentIrpStackLocation( Irp );
    inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
    outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;
    if (!inBufLength || !outBufLength || outBufLength != sizeof(unsigned __int64)*2)
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        goto End;
    }
    //
    // Determine which I/O control code was specified.
    //
    switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )
    {
    case IOCTL_SIOCTL_METHOD_BUFFERED:
        //
        // In this method the I/O manager allocates a buffer large enough to
        // to accommodate larger of the user input buffer and output buffer,
        // assigns the address to Irp->AssociatedIrp.SystemBuffer, and
        // copies the content of the user input buffer into this SystemBuffer
        //
        DRIVER_PRINT(("Called IOCTL_SIOCTL_METHOD_BUFFEREDn"));
        PrintIrpInfo(Irp);
        //
        // Input buffer and output buffer is same in this case, read the
        // content of the buffer before writing to it
        //
        inBuf = (void *)Irp->AssociatedIrp.SystemBuffer;
        outBuf = (unsigned __int64 *)Irp->AssociatedIrp.SystemBuffer;
        //
        // Read the data from the buffer
        //
        DRIVER_PRINT(("tData from User :"));
        //
        // We are using the following function to print characters instead
        // DebugPrint with %s format because we string we get may or
        // may not be null terminated.
        //
        PrintChars(inBuf, inBufLength);
        //
        // Write to the buffer
        //
        unsigned __int64 data[sizeof(unsigned __int64) * 2];
        data[0] = __readmsr(232);
        data[1] = __readmsr(231);
        DRIVER_PRINT(("data[0]: %d", data[0]));
        DRIVER_PRINT(("data[1]: %d", data[1]));
        RtlCopyBytes(outBuf, data, outBufLength);
        //
        // Assign the length of the data copied to IoStatus.Information
        // of the Irp and complete the Irp.
        //
        Irp->IoStatus.Information = sizeof(unsigned __int64)*2;
        //
        // When the Irp is completed the content of the SystemBuffer
        // is copied to the User output buffer and the SystemBuffer is
        // is freed.
        //
       break;
    default:
        //
        // The specified I/O control code is unrecognized by this driver.
        //
        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
        DRIVER_PRINT(("ERROR: unrecognized IOCTL %xn",
            irpSp->Parameters.DeviceIoControl.IoControlCode));
        break;
    }
End:
    //
    // Finish the I/O operation by simply completing the packet and returning
    // the same status as in the packet itself.
    //
    Irp->IoStatus.Status = ntStatus;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return ntStatus;
}
VOID
PrintIrpInfo(
    PIRP Irp)
{
    PIO_STACK_LOCATION  irpSp;
    irpSp = IoGetCurrentIrpStackLocation( Irp );
    PAGED_CODE();
    DRIVER_PRINT(("tIrp->AssociatedIrp.SystemBuffer = 0x%pn",
        Irp->AssociatedIrp.SystemBuffer));
    DRIVER_PRINT(("tIrp->UserBuffer = 0x%pn", Irp->UserBuffer));
    DRIVER_PRINT(("tirpSp->Parameters.DeviceIoControl.Type3InputBuffer = 0x%pn",
        irpSp->Parameters.DeviceIoControl.Type3InputBuffer));
    DRIVER_PRINT(("tirpSp->Parameters.DeviceIoControl.InputBufferLength = %dn",
        irpSp->Parameters.DeviceIoControl.InputBufferLength));
    DRIVER_PRINT(("tirpSp->Parameters.DeviceIoControl.OutputBufferLength = %dn",
        irpSp->Parameters.DeviceIoControl.OutputBufferLength ));
    return;
}
VOID
PrintChars(
    _In_reads_(CountChars) PCHAR BufferAddress,
    _In_ size_t CountChars
    )
{
    PAGED_CODE();
    if (CountChars) {
        while (CountChars--) {
            if (*BufferAddress > 31
                 && *BufferAddress != 127) {
                KdPrint (( "%c", *BufferAddress) );
            } else {
                KdPrint(( ".") );
            }
            BufferAddress++;
        }
        KdPrint (("n"));
    }
    return;
}

驱动程序.h

//
// Device type           -- in the "User Defined" range."
//
#define SIOCTL_TYPE 40000
//
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
//
#define IOCTL_SIOCTL_METHOD_IN_DIRECT 
    CTL_CODE( SIOCTL_TYPE, 0x900, METHOD_IN_DIRECT, FILE_ANY_ACCESS  )
#define IOCTL_SIOCTL_METHOD_OUT_DIRECT 
    CTL_CODE( SIOCTL_TYPE, 0x901, METHOD_OUT_DIRECT , FILE_ANY_ACCESS  )
#define IOCTL_SIOCTL_METHOD_BUFFERED 
    CTL_CODE( SIOCTL_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS  )
#define IOCTL_SIOCTL_METHOD_NEITHER 
    CTL_CODE( SIOCTL_TYPE, 0x903, METHOD_NEITHER , FILE_ANY_ACCESS  )

#define DRIVER_FUNC_INSTALL     0x01
#define DRIVER_FUNC_REMOVE      0x02
#define DRIVER_NAME       "ReadMSRDriver"

现在,这里是加载并使用驱动程序的应用程序(Win32控制台应用程序):

频率计算器.cpp

#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strsafe.h>
#include <process.h>
#include "..KernelModeDriverdriver.h"
using namespace std;
BOOLEAN
ManageDriver(
_In_ LPCTSTR  DriverName,
_In_ LPCTSTR  ServiceName,
_In_ USHORT   Function
);
HANDLE hDevice;
TCHAR driverLocation[MAX_PATH];
void InstallDriver()
{
    DWORD errNum = 0;
    GetCurrentDirectory(MAX_PATH, driverLocation);
    _tcscat_s(driverLocation, _T("\KernelModeDriver.sys"));
    std::wcout << "Trying to install driver at " << driverLocation << std::endl;
    //
    // open the device
    //
    if ((hDevice = CreateFile(_T("\\.\KernelModeDriver"),
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL)) == INVALID_HANDLE_VALUE) {
        errNum = GetLastError();
        if (errNum != ERROR_FILE_NOT_FOUND) {
            printf("CreateFile failed!  ERROR_FILE_NOT_FOUND = %dn", errNum);
            return;
        }
        //
        // The driver is not started yet so let us the install the driver.
        // First setup full path to driver name.
        //
        if (!ManageDriver(_T(DRIVER_NAME),
            driverLocation,
            DRIVER_FUNC_INSTALL
            )) {
            printf("Unable to install driver. n");
            //
            // Error - remove driver.
            //
            ManageDriver(_T(DRIVER_NAME),
                driverLocation,
                DRIVER_FUNC_REMOVE
                );
            return;
        }
        hDevice = CreateFile(_T("\\.\KernelModeDriver"),
            GENERIC_READ | GENERIC_WRITE,
            0,
            NULL,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
        if (hDevice == INVALID_HANDLE_VALUE){
            printf("Error: CreatFile Failed : %dn", GetLastError());
            return;
        }
    }
}
void UninstallDriver()
{
    //
    // close the handle to the device.
    //
    CloseHandle(hDevice);
    //
    // Unload the driver.  Ignore any errors.
    //
    ManageDriver(_T(DRIVER_NAME),
        driverLocation,
        DRIVER_FUNC_REMOVE
        );
}
double GetPerformanceRatio()
{
    BOOL bRc;
    ULONG bytesReturned;
    int input = 0;
    unsigned __int64 output[2];
    memset(output, 0, sizeof(unsigned __int64) * 2);
    //printf("InputBuffer Pointer = %p, BufLength = %dn", &input, sizeof(&input));
    //printf("OutputBuffer Pointer = %p BufLength = %dn", &output, sizeof(&output));
    //
    // Performing METHOD_BUFFERED
    //
    //printf("nCalling DeviceIoControl METHOD_BUFFERED:n");
    bRc = DeviceIoControl(hDevice,
        (DWORD)IOCTL_SIOCTL_METHOD_BUFFERED,
        &input,
        sizeof(&input),
        output,
        sizeof(unsigned __int64)*2,
        &bytesReturned,
        NULL
        );
    if (!bRc)
    {
        //printf("Error in DeviceIoControl : %d", GetLastError());
        return 0;
    }
    //printf("    OutBuffer (%d): %dn", bytesReturned, output);
    if (output[1] == 0)
    {
        return 0;
    }
    else
    {
        return (float)output[0] / (float)output[1];
    }
}
struct Core
{
    int CoreNumber;
};
int GetNumberOfProcessorCores()
{
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    return sysinfo.dwNumberOfProcessors;
}
float GetCoreFrequency()
{
    // __rdtsc: Returns the processor time stamp which records the number of clock cycles since the last reset.
    // QueryPerformanceCounter: Returns a high resolution time stamp that can be used for time-interval measurements.
    // Get the frequency which defines the step size of the QueryPerformanceCounter method.
    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);
    // Get the number of cycles before we start.
    ULONG cyclesBefore = __rdtsc();
    // Get the Intel performance ratio at the start.
    float ratioBefore = GetPerformanceRatio();
    // Get the start time.
    LARGE_INTEGER startTime;
    QueryPerformanceCounter(&startTime);
    // Give the CPU cores enough time to repopulate their __rdtsc and QueryPerformanceCounter registers.
    Sleep(1000);
    ULONG cyclesAfter = __rdtsc();
    // Get the Intel performance ratio at the end.
    float ratioAfter = GetPerformanceRatio();
    // Get the end time.
    LARGE_INTEGER endTime;
    QueryPerformanceCounter(&endTime);
    // Return the number of MHz. Multiply the core's frequency by the mean MSR (model-specific register) ratio (the APERF register's value divided by the MPERF register's value) between the two timestamps.
    return ((ratioAfter + ratioBefore) / 2)*(cyclesAfter - cyclesBefore)*pow(10, -6) / ((endTime.QuadPart - startTime.QuadPart) / frequency.QuadPart);
}
struct CoreResults
{
    int CoreNumber;
    float CoreFrequency;
};
CRITICAL_SECTION printLock;
static void printResult(void *param)
{
    EnterCriticalSection(&printLock);
    CoreResults coreResults = *((CoreResults *)param);
    std::cout << "Core " << coreResults.CoreNumber << " has a speed of " << coreResults.CoreFrequency << " MHz" << std::endl;
    delete param;
    LeaveCriticalSection(&printLock);
}
bool closed = false;
static void startMonitoringCoreSpeeds(void *param)
{
    Core core = *((Core *)param);
    SetThreadAffinityMask(GetCurrentThread(), 1 << core.CoreNumber);
    while (!closed)
    {
        CoreResults *coreResults = new CoreResults();
        coreResults->CoreNumber = core.CoreNumber;
        coreResults->CoreFrequency = GetCoreFrequency();
        _beginthread(printResult, 0, coreResults);
        Sleep(1000);
    }
    delete param;
}
int _tmain(int argc, _TCHAR* argv[])
{
    InitializeCriticalSection(&printLock);
    InstallDriver();
    for (int i = 0; i < GetNumberOfProcessorCores(); i++)
    {
        Core *core = new Core{ 0 };
        core->CoreNumber = i;
        _beginthread(startMonitoringCoreSpeeds, 0, core);
    }
    std::cin.get();
    closed = true;
    UninstallDriver();
    DeleteCriticalSection(&printLock);
}

它使用install.cpp,您可以从IOCTL示例中获得它。在接下来的几天里,我将在我的博客上发布一个有效的、完全有效的、现成的解决方案(显然是有代码的),如果不是今晚的话。

编辑:在http://www.dima.to/blog/?p=101(那里有完整的源代码)