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_BIND
MSVCにないものを使用せずに)。
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));
}