簡體   English   中英

如何以C編程方式查找CPU頻率

[英]How can I programmatically find the CPU frequency with C

我試圖找出是否有任何想法來了解我的C代碼正在運行的系統的CPU頻率。

為了澄清,我正在尋找一個抽象的解決方案(一個不會與特定架構或操作系統綁定的解決方案),它可以讓我了解我的代碼正在執行的計算機的運行頻率。 我不需要准確,但我想進入球場(即我有一個2.2GHz的處理器,我希望能夠在我的程序中告訴我我在幾百之內)那個MHz)

有沒有人有想法使用標准C代碼?

可以找到一個通用解決方案,它可以正確地為一個線程或多個線程獲取工作頻率。 這不需要管理員/ root權限或訪問模型特定的寄存器。 我已經在Linux和Windows上對英特爾處理器進行了測試,包括Nahalem,Ivy Bridge和Haswell,一個插槽最多四個插槽(40個線程)。 結果與正確答案的偏差均小於0.5%。 在我向您展示如何做之前,讓我展示一下結果(來自GCC 4.9和MSVC2013):

Linux:    E5-1620 (Ivy Bridge) @ 3.60GHz    
1 thread: 3.789, 4 threads: 3.689 GHz:  (3.8-3.789)/3.8 = 0.3%, 3.7-3.689)/3.7 = 0.3%

Windows:  E5-1620 (Ivy Bridge) @ 3.60GHz
1 thread: 3.792, 4 threads: 3.692 GHz: (3.8-3.789)/3.8 = 0.2%, (3.7-3.689)/3.7 = 0.2%

Linux:  4xE7-4850 (Nahalem) @ 2.00GHz
1 thread: 2.390, 40 threads: 2.125 GHz:, (2.4-2.390)/2.4 = 0.4%, (2.133-2.125)/2.133 = 0.4%

Linux:    i5-4250U (Haswell) CPU @ 1.30GHz
1 thread: within 0.5% of 2.6 GHz, 2 threads wthin 0.5% of 2.3 GHz

Windows: 2xE5-2667 v2 (Ivy Bridge) @ 3.3 GHz
1 thread: 4.000 GHz, 16 threads: 3.601 GHz: (4.0-4.0)/4.0 = 0.0%, (3.6-3.601)/3.6 = 0.0%

我從這個鏈接中得到了這個想法http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/

要做到這一點,你首先要做的就是你20年前做的事情。 你用循環編寫一些代碼,你知道它的延遲和時間。 這是我用過的:

static int inline SpinALot(int spinCount)
{
    __m128 x = _mm_setzero_ps();
    for(int i=0; i<spinCount; i++) {
        x = _mm_add_ps(x,_mm_set1_ps(1.0f));
    }
    return _mm_cvt_ss2si(x);
}

這具有承載循環依賴性,因此CPU無法對此進行重新排序以減少延遲。 每次迭代總是需要3個時鍾周期。 操作系統不會將線程遷移到另一個核心,因為我們將綁定線程。

然后在每個物理核心上運行此功能。 我用OpenMP做到了這一點。 必須為此綁定線程。 在使用GCC的linux中,您可以使用export OMP_PROC_BIND=true來綁定線程,並假設您有ncores物理核心也可以export OMP_NUM_THREADS=ncores 如果您想以編程方式綁定並查找英特爾處理器的物理內核數量,請參閱此編程檢測數量的物理處理器核心或如果超線程線程關聯性與Windows-msvc-和 - openmp

void sample_frequency(const int nsamples, const int n, float *max, int nthreads) {
    *max = 0;
    volatile int x = 0;
    double min_time = DBL_MAX;
    #pragma omp parallel reduction(+:x) num_threads(nthreads)
    {
        double dtime, min_time_private = DBL_MAX;
        for(int i=0; i<nsamples; i++) {
             #pragma omp barrier
             dtime = omp_get_wtime();
             x += SpinALot(n);
             dtime = omp_get_wtime() - dtime;
             if(dtime<min_time_private) min_time_private = dtime;
        }
        #pragma omp critical
        {
            if(min_time_private<min_time) min_time = min_time_private;
        }
    }
    *max = 3.0f*n/min_time*1E-9f;
}

最后在循環中運行采樣器並打印結果

int main(void) {
    int ncores = getNumCores();
    printf("num_threads %d, num_cores %d\n", omp_get_max_threads(), ncores);       
    while(1) {
        float max1, median1, max2, median2;
        sample_frequency(1000, 1000000, &max2, &median2, ncores);
        sample_frequency(1000, 1000000, &max1, &median1,1);          
        printf("1 thread: %.3f, %d threads: %.3f GHz\n" ,max1, ncores, max2);
    }
}

我沒有在AMD處理器上測試過這個。 我認為帶有模塊的AMD處理器(例如Bulldozer)必須綁定到每個模塊而不是每個AMD“核心”。 這可以通過GCC export GOMP_CPU_AFFINITY來完成。 您可以在https://bitbucket.org/zboson/frequency找到一個完整的工作示例,它可以在英特爾處理器上的Windows和Linux上運行,並且可以正確地找到英特爾處理器的物理內核數量(至少自Nahalem以來)並將它們綁定到每個物理核心(不使用MSVC沒有的OMP_PROC_BIND )。


由於SSE,AVX和AVX512的頻率調整不同,這種方法必須針對現代處理器進行一些修改。

這是我在修改我的方法(參見表后代碼)后得到的一個新表,它有四個Xeon 6142處理器(每個處理器16個內核)。

        sums  1-thread  64-threads
SSE        1       3.7         3.3
SSE        8       3.7         3.3
AVX        1       3.7         3.3
AVX        2       3.7         3.3
AVX        4       3.6         2.9
AVX        8       3.6         2.9
AVX512     1       3.6         2.9
AVX512     2       3.6         2.9
AVX512     4       3.5         2.2
AVX512     8       3.5         2.2

這些數字與此表中的頻率一致https://en.wikichip.org/wiki/intel/xeon_gold/6142#Frequencies

有趣的是,我現在需要至少做4個並行求和來實現更低的頻率。 Skylake上的addps延遲是4個時鍾周期。 這些可以轉到兩個端口(AVX512端口0和1保險絲計數,一個AVX512端口,其他AVX512操作轉到端口5)。

這是我如何做八個平行和。

static int inline SpinALot(int spinCount) {
  __m512 x1 = _mm512_set1_ps(1.0);
  __m512 x2 = _mm512_set1_ps(2.0);
  __m512 x3 = _mm512_set1_ps(3.0);
  __m512 x4 = _mm512_set1_ps(4.0);
  __m512 x5 = _mm512_set1_ps(5.0);
  __m512 x6 = _mm512_set1_ps(6.0);
  __m512 x7 = _mm512_set1_ps(7.0);
  __m512 x8 = _mm512_set1_ps(8.0);
  __m512 one = _mm512_set1_ps(1.0);
  for(int i=0; i<spinCount; i++) {
    x1 = _mm512_add_ps(x1,one);
    x2 = _mm512_add_ps(x2,one);
    x3 = _mm512_add_ps(x3,one);
    x4 = _mm512_add_ps(x4,one);
    x5 = _mm512_add_ps(x5,one);
    x6 = _mm512_add_ps(x6,one);
    x7 = _mm512_add_ps(x7,one);
    x8 = _mm512_add_ps(x8,one);
  }
  __m512 t1 = _mm512_add_ps(x1,x2);
  __m512 t2 = _mm512_add_ps(x3,x4);
  __m512 t3 = _mm512_add_ps(x5,x6);
  __m512 t4 = _mm512_add_ps(x7,x8);
  __m512 t6 = _mm512_add_ps(t1,t2);
  __m512 t7 = _mm512_add_ps(t3,t4);
  __m512  x = _mm512_add_ps(t6,t7);
  return _mm_cvt_ss2si(_mm512_castps512_ps128(x));
}

為了完整起見,已經有一個簡單,快速,准確的用戶模式解決方案,具有巨大的缺點:它僅適用於Intel Skylake,Kabylake和更新的處理器。 確切的要求是CPUID級別16h支持。 根據英特爾軟件開發人員手冊325462第59版,第770頁:

  • CPUID.16h.EAX =處理器基頻(MHz);

  • CPUID.16h.EBX =最大頻率(MHz);

  • CPUID.16h.ECX =總線(參考)頻率(以MHz為單位)。

Visual Studio 2015示例代碼:

#include <stdio.h>
#include <intrin.h>

int main(void) {
    int cpuInfo[4] = { 0, 0, 0, 0 };
    __cpuid(cpuInfo, 0);
    if (cpuInfo[0] >= 0x16) {
        __cpuid(cpuInfo, 0x16);

        //Example 1
        //Intel Core i7-6700K Skylake-H/S Family 6 model 94 (506E3)
        //cpuInfo[0] = 0x00000FA0; //= 4000 MHz
        //cpuInfo[1] = 0x00001068; //= 4200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 2
        //Intel Core m3-6Y30 Skylake-U/Y Family 6 model 78 (406E3)
        //cpuInfo[0] = 0x000005DC; //= 1500 MHz
        //cpuInfo[1] = 0x00000898; //= 2200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 3
        //Intel Core i5-7200 Kabylake-U/Y Family 6 model 142 (806E9)
        //cpuInfo[0] = 0x00000A8C; //= 2700 MHz
        //cpuInfo[1] = 0x00000C1C; //= 3100 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        printf("EAX: 0x%08x EBX: 0x%08x ECX: %08x\r\n", cpuInfo[0], cpuInfo[1], cpuInfo[2]);
        printf("Processor Base Frequency:  %04d MHz\r\n", cpuInfo[0]);
        printf("Maximum Frequency:         %04d MHz\r\n", cpuInfo[1]);
        printf("Bus (Reference) Frequency: %04d MHz\r\n", cpuInfo[2]);
    } else {
        printf("CPUID level 16h unsupported\r\n");
    }
    return 0;
}

您如何找到CPU頻率取決於體系結構和操作系統,並且沒有抽象的解決方案。

如果我們20多年前你使用的是沒有上下文切換的操作系統並且CPU按順序執行給出的指令,你可以在循環中編寫一些C代碼並計時,然后根據匯編編譯成的程序集計算運行時的指令數。 這已經假設每條指令需要1個時鍾周期,這是自流水線處理器以來的一個相當差的假設。

但任何現代操作系統都會在多個進程之間切換 即使這樣,您也可以嘗試為一系列相同for循環運行計時(忽略頁面錯誤所需的時間以及處理器可能停滯的多種其他原因)並獲得中值。

即使以前的解決方案有效,您也可以使用多個處理器。 使用任何現代處理器,重新訂購指令,在同一時鍾周期內發布一堆指令,甚至在核心之間拆分它們都是公平的游戲。

CPU頻率與硬件相關,因此沒有可用於獲取它的通用方法,它還取決於您使用的操作系統。

例如,如果您使用的是Linux,則可以讀取文件/ proc / cpuinfo ,也可以解析dmesg啟動日志以獲取此值,或者如果您希望可以看到Linux內核如何處理這些內容並嘗試自定義代碼滿足您的需求:

https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/proc.c

問候。

我想從軟件中獲取時鍾頻率的一種方法是將硬件參考手冊(HRM)的硬編碼硬編碼到軟件中。 您可以從軟件中讀取時鍾配置寄存器。 假設您知道源時鍾頻率,軟件可以使用時鍾寄存器中的乘法器和除數值,並應用HRM中提到的適當公式來推導時鍾頻率。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM