17

私のCコードが実行されているシステムのCPU周波数を知る方法があるかどうかを調べようとしています。

明確にするために、コードが実行されているコンピューターの動作頻度を知ることができる抽象的なソリューション(特定のアーキテクチャーやOSに関連付けられていないソリューション)を探しています。正確である必要はありませんが、球場に行きたいです(つまり、2.2 GHzプロセッサを使用しているので、プログラムで数百以内であることを伝えたいと思います。そのMHz)

標準のCコードを使用するアイデアを持っている人はいますか?

4

5 に答える 5

16

完全を期すために、すでに、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;
}
于 2016-09-21T14:57:40.867 に答える
15

1つのスレッドまたは複数のスレッドの動作周波数を正しく取得する一般的な解決策を見つけることができます。これには、admin/root権限やモデル固有のレジスタへのアクセスは必要ありません。これは、Nahalem、Ivy Bridge、HaswellなどのIntelプロセッサ上のLinuxおよびWindowsで、1つのソケットから最大4つのソケット(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クロックサイクルかかります。スレッドをバインドするため、OSはスレッドを別のコアに移行しません。

次に、この関数を各物理コアで実行します。私はこれをOpenMPで行いました。スレッドはこれにバインドする必要があります。LinuxでGCCを使用export OMP_PROC_BIND=trueすると、スレッドをバインドするために使用でき、ncores物理コアがあると仮定して実行することもできexport OMP_NUM_THREADS=ncoresます。プログラムでバインドしてIntelプロセッサの物理コアの数を確認する場合は、このプログラムで検出する物理プロセッサのコアの数またはハイパースレッディングスレッドアフィニティとウィンドウの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プロセッサ(ブルドーザーなど)は、各AMD「コア」ではなく各モジュールにバインドする必要があると思います。export GOMP_CPU_AFFINITYこれはGCCで行うことができます。完全に機能する例はhttps://bitbucket.org/zboson/frequencyで見つけることができます。これは、Intelプロセッサ上のWindowsおよびLinuxで動作し、Intelプロセッサの物理コアの数(少なくともNahalem以降)を正しく検出し、それらをにバインドします。各物理コア(OMP_PROC_BINDMSVCにないものを使用せずに)。


SSE、AVX、およびAVX512の周波数スケーリングが異なるため、この方法は最近のプロセッサでは少し変更する必要があります。

これは、4つの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クロックサイクルです。これらは2つのポートに移動できます(AVX512ポート0と1のヒューズでカウントし、1つのAVX512ポートと他のAVX512操作はポート5に移動します)。

これが私が8つの並列合計を行った方法です。

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));
}
于 2014-08-20T08:36:57.863 に答える
7

CPU周波数を見つける方法は、アーキテクチャとOSの両方に依存し、抽象的な解決策はありません。

20年以上前に、コンテキスト切り替えのないOSを使用していて、CPUが指定された命令を順番に実行した場合、ループでCコードを記述して時間を計り、アセンブリに基づいてコンパイルすることができます。実行時に命令の数を計算します。これは、各命令が1クロックサイクルかかるという仮定をすでに行っています。これは、パイプライン化されたプロセッサ以来、かなり貧弱な仮定です。

しかし、最近のOSは、複数のプロセスを切り替えます。それでも、一連の同一のforループ実行の時間を計り(ページフォールトに必要な時間や、プロセッサがストールする可能性がある他の複数の理由を無視して)、中央値を取得することができます。

また、前のソリューションが機能する場合でも、複数の問題のプロセッサーがあります。最新のプロセッサでは、命令を並べ替えたり、同じクロックサイクルで大量の命令を発行したり、コア間で分割したりするのは公正なゲームです。

于 2012-07-29T04:30:08.490 に答える
2

CPU周波数はハードウェアに関連するものであるため、CPU周波数を取得するために適用できる一般的な方法はなく、使用しているOSによっても異なります。

たとえば、Linuxを使用している場合は、ファイル/ proc / cpuinfoを読み取るか、 dmesgブートログを解析してこの値を取得できます。または、Linuxカーネルがこれらを処理する方法を確認して、コードをカスタマイズしてみてください。あなたのニーズを満たすために:

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

よろしく。

于 2012-07-29T04:59:30.440 に答える
0

ソフトウェアからクロック周波数を取得する1つの方法は、ハードウェアリファレンスマニュアル(HRM)の知識をソフトウェアにハードコーディングすることだと思います。ソフトウェアからクロックコンフィギュレーションレジスタを読み取ることができます。ソースクロック周波数がわかっていると仮定すると、ソフトウェアはクロックレジスタからの乗数と除数の値を使用し、HRMに記載されている適切な式を適用してクロック周波数を導出できます。

于 2017-03-06T17:19:49.973 に答える