プロセッサの周波数を測定するために使用されている一般的なアルゴリズムは何ですか?
10 に答える
Core Duo 以降の Intel CPU は、IA32_MPERF および IA32_APERF と呼ばれる 2 つのモデル固有のレジスタをサポートします。
MPERF は CPU がサポートする最大周波数でカウントしますが、APERF は実際の現在の周波数でカウントします。
実際の周波数は次の式で与えられます。
この流れで読めます
; read MPERF
mov ecx, 0xe7
rdmsr
mov mperf_var_lo, eax
mov mperf_var_hi, edx
; read APERF
mov ecx, 0xe8
rdmsr
mov aperf_var_lo, eax
mov aperf_var_hi, edx
ただし、rdmsr は特権命令であり、リング 0 でのみ実行できることに注意してください。
OSがこれらを読み取るためのインターフェースを提供しているかどうかはわかりませんが、主な用途は電源管理であるため、そのようなインターフェースを提供していない可能性があります。
この回答では、さまざまな詳細で自分自身とデートしますが、一体何...
私は何年も前に Windows ベースの PC でこの問題に取り組む必要があったため、486、Pentium などの Intel x86 シリーズ プロセッサを扱っていました。そのような状況での標準的なアルゴリズムは、長い一連の DIVide 命令を実行することでした。これらは通常、Intel セットで最も CPU バウンドの単一命令であるためです。したがって、メモリのプリフェッチやその他のアーキテクチャの問題は、命令の実行時間に実質的な影響を与えません。プリフェッチ キューは常にいっぱいで、命令自体は他のメモリに触れません。
実行している環境でアクセスできる最高解像度のクロックを使用して時間を計ります。実際の OS では推奨されていますが、通常、最近では呼び出す適切な API がいくつかあります)。
対処しなければならない主な問題は、さまざまな CPU タイプです。当時、Intel、AMD、および x86 プロセッサを製造している Cyrix のようないくつかの小規模なベンダーがありました。各モデルには、その DIV 命令に対して独自のパフォーマンス特性がありました。私のアセンブリ タイミング関数は、タイトなループで実行される特定の固定数の DIV 命令によって使用されるクロック サイクル数を返すだけです。
そこで私がしたことは、測定したい各プロセッサ モデルを実行している実際の PC からいくつかのタイミング (その関数からの生の戻り値) を収集し、それらを既知のプロセッサ速度とプロセッサ タイプに対してスプレッドシートに記録することでした。私は実際に、タイミング機能に関する単なるシェルであるコマンド ライン ツールを使用していました。ディスクをコンピュータ ストアに持ち込んで、ディスプレイ モデルからタイミングを取得していました。(当時、私は非常に小さな会社で働いていました)。
これらの生のタイミングを使用して、その特定の CPU の既知の速度に対して取得すべきタイミングの理論的なグラフをプロットできました。
ここに秘訣があります。ユーティリティを実行すると、CPU が 99.8 Mhz か何かであるとアナウンスされるのがいつも嫌いでした。明らかに 100 Mhz であり、測定値にはわずかな丸め誤差がありました。スプレッドシートに、各プロセッサ ベンダーが販売した実際の速度を記録しました。次に、実際のタイミングのプロットを使用して、既知の速度の予測タイミングを推定します。しかし、タイミングが次の速度に丸められる線に沿ってポイントのテーブルを作成します。
言い換えれば、繰り返し除算を行うための 100 ティックが 500 Mhz を意味し、200 ティックが 250 Mhz を意味する場合、150 未満のものはすべて 500 Mhz であり、それを超えるものはすべて 250 Mhz であるという表を作成します。(そのチップ ベンダーから入手できる速度は、これらの 2 つだけであると仮定します)。PC 上の奇妙なソフトウェアの一部が私のタイミングを狂わせていたとしても、最終結果はしばしば完全に機能しないため、それは良かったです.
もちろん、オーバークロック、電源管理のための動的クロック速度、およびその他のそのようなトリックの最近では、そのようなスキームははるかに実用的ではありません. 少なくとも、タイミング関数を実行する前に、CPU が動的に選択された最高速度であることを確認するために何かをする必要があります。
OK、子供たちを芝生から追い出すことに戻ります。
Pentium 以降の x86 Intel CPU での 1 つの方法は、既知の壁時間の遅延ループで RDTSC 命令の 2 つのサンプリングを使用することです。
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
uint64_t rdtsc(void) {
uint64_t result;
__asm__ __volatile__ ("rdtsc" : "=A" (result));
return result;
}
int main(void) {
uint64_t ts0, ts1;
ts0 = rdtsc();
sleep(1);
ts1 = rdtsc();
printf("clock frequency = %llu\n", ts1 - ts0);
return 0;
}
(GCC を使用する 32 ビット プラットフォーム上)
CR4 の TSC フラグが設定されている場合、RDTSC はリング 3 で使用できます。これは一般的ですが、保証されていません。この方法の欠点の 1 つは、周波数スケーリングの変更が遅延内で発生した場合に、結果に影響を与える可能性があることです。これを緩和するために、CPU をビジー状態に保つコードを実行し、システム時間を常にポーリングして遅延期間が経過したかどうかを確認し、CPU を利用可能な最高周波数の状態に保つことができます。
次の(疑似)アルゴリズムを使用します。
basetime=time(); /* time returns seconds */
while (time()==basetime);
stclk=rdtsc(); /* rdtsc is an assembly instruction */
basetime=time();
while (time()==basetime
endclk=rdtsc();
nclks=encdclk-stclk;
この時点で、クロック周波数を決定したと思うかもしれませんが、正しいように見えても、改善することができます。
すべての PC には、シリアル ポートとシステム クロックに (以前は) 使用されていたカウンターを含む PIT (プログラマブル インターバル タイマー) デバイスが含まれています。1193182 Hzの周波数が供給されました。システム クロック カウンターが最高のカウントダウン値 (65536) に設定されたため、システム クロックのティック周波数は 1193182/65536 => 18.2065 Hz、つまり 54.925 ミリ秒ごとに 1 回になりました。
したがって、クロックが次の秒にインクリメントするために必要なティック数は依存します。通常は 18 ティック、場合によっては 19 ティックが必要です。これは、アルゴリズム (上記) を 2 回実行し、結果を保存することで処理できます。2 つの結果は、2 つの 18 ティック シーケンス、または 1 つの 18 と 1 つの 19 のいずれかと同等になります。連続した 2 つの 19 は発生しません。したがって、2 つの結果のうち小さい方を取得すると、18 ティック秒になります。この結果を調整するには、18.2065 を掛けて 18.0 で割るか、整数演算を使用して 182065 を掛け、90000 を足して 180000 で割ります。90000 は 180000 の半分であり、丸められます。整数ルートによる計算を選択した場合は、64 ビットの乗算と除算を使用していることを確認してください。
これで、kHz ((x+500)/1000) または MHz ((x+5000000)/1000000) に変換できる Hz 単位の CPU クロック速度 x が得られます。500 と 500000 は、それぞれ 1000 と 1000000 の半分であり、丸めのためにあります。丸めの問題が発生する可能性があるため、MHz を計算するには、kHz 値を使用しないでください。Hz 値と 2 番目のアルゴリズムを使用します。
それはBogoMIPSのようなものの意図でしたが、CPUは最近はるかに複雑になっています。スーパースカラーCPUは、クロックごとに複数の命令を発行する可能性があり、クロックサイクルのカウントに基づいて測定を行い、命令のブロックを非常に不正確に実行します。
CPU周波数も、提供される負荷や温度に基づいて変化します。CPUが現在800MHzで実行されているという事実は、CPUが常に800 MHzで実行されていることを意味するわけではなく、必要に応じてスロットルアップまたはスロットルダウンする可能性があります。
本当にクロック周波数を知る必要がある場合は、パラメータとして渡す必要があります。ボード上のEEPROMが基本周波数を供給します。クロックが変化する可能性がある場合は、CPUの電源状態レジスタを読み取って(またはOSを呼び出して)、その瞬間の周波数を確認できる必要があります。
そうは言っても、あなたがやろうとしていることを達成する他の方法があるかもしれません。たとえば、特定のコードパスにかかる時間を高精度で測定する場合、CPUには、ティックカウントレジスタを読み取るよりも優れた実時間の測定値である固定周波数で実行されるパフォーマンスカウンタがある可能性があります。
「lmbench」は、異なるアーキテクチャに移植可能な CPU 周波数アルゴリズムを提供します。
いくつかの異なるループを実行し、プロセッサのクロック速度は、さまざまなループの実行頻度の最大公約数です。
この方法は、サイクル数が比較的素数のループを取得できる場合に常に機能するはずです。
1 つのオプションは、ループごとに既知の命令でコードを実行することにより、CPU 周波数を感知することです。
この機能は、v9.20 くらいから 7zip に含まれていると思います。
> 7z b
7-Zip 9.38 beta Copyright (c) 1999-2014 Igor Pavlov 2015-01-03
CPU Freq: 4266 4000 4266 4000 2723 4129 3261 3644 3362
最終的な数値は正しいはずです (そして、私の PC や他の多くの PC では、かなり正確であることがわかりました。テストは非常に高速に実行されるため、ターボが起動しない可能性があり、バランス/省電力モードに設定されたサーバーは、ほとんどの場合、約 1 GHz の測定値)
ソースコードはGitHubにあります(公式ソースは 7-zip.org からダウンロードしたものです)
最も重要な部分は次のとおりです。
#define YY1 sum += val; sum ^= val;
#define YY3 YY1 YY1 YY1 YY1
#define YY5 YY3 YY3 YY3 YY3
#define YY7 YY5 YY5 YY5 YY5
static const UInt32 kNumFreqCommands = 128;
EXTERN_C_BEGIN
static UInt32 CountCpuFreq(UInt32 sum, UInt32 num, UInt32 val)
{
for (UInt32 i = 0; i < num; i++)
{
YY7
}
return sum;
}
EXTERN_C_END
なぜこのために組み立てが必要なのかわかりません。/ procファイルシステムを備えたマシンを使用している場合は、次のコマンドを実行します。
> cat /proc/cpuinfo
あなたが必要なものをあなたに与えるかもしれません。