このブログは、株式会社フィックスターズのエンジニアが、あらゆるテーマについて自由に書いているブログです。
近年のCPUは電力削減のために、負荷にあわせてクロックが変動するものが多くなっています。
rdtsc, rdtscp で取得できる値は、このクロックとは別にカウントされており、省電力機能の有無に関わらず、一定クロックでカウンタが増えていきます(一時期はそうでないCPUもありましたが64bit x86 CPUは全て一定クロックになっているとみなしてよいです)。このため、特定の処理にかかるクロックを求める場合には使いづらい場合があります。
また、前回書いたように、rdtscpで読める値は、コア毎に値が違うため、OSのコンテキストスイッチのタイミングによっては、正しくない値が取得される可能性があります。
この問題をなんとかするために、CPU_CLK_UNHALTEDの値を、Linuxのperf_eventインターフェースを使って読んでみることにします。
x86を含む最近のCPUには、パフォーマンスを計測するためのカウンタが実装されている場合が多いです。CPU_CLK_UNHALTEDは、このパフォーマンスカウンタを使って計測できる値のひとつで、省電力機能によるクロック変動を含めた、実際のCPUクロックの値です。
x86ではパフォーマンスカウンタの設定、および、値は、OSの保護下にあるので、ユーザープログラムから直接操作することはできませんが、Linuxには、これを操作するためのインターフェースが用意されています。このインターフェースの正確な名前はわからないのですが、ヘッダファイルの名前にあわせて、perf_eventインターフェースと呼ぶことにします。
このperf_eventインターフェースを使うと、CPU_CLK_UNHALTEDの値を取得できます。また、このperf_event経由で取得できる値は、OSの管理下にあるので、コンテキストスイッチが行われた場合でも、値が大きくずれることはなくなります。
それではこれを使ってみましょう。perf_eventを使ってCPU_CLK_UNHALTEDを読むサンプルは以下になります。
perf_event_openシステムコールインターフェースは標準では用意されないので、perf_event_openシステムコールを使えるようにします
static int
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags )
{
int ret;
ret = syscall( __NR_perf_event_open, hw_event, pid, cpu,
group_fd, flags );
return ret;
}
次に、perf_event_attr構造体を使って、カウンタの設定を決めます。
memset(&attr, 0, sizeof(attr));
attr.type = PERF_TYPE_HARDWARE;
attr.size = sizeof(attr);
attr.config = PERF_COUNT_HW_CPU_CYCLES;
このようにすると、CPU_CLK_UNHALTEDの値を読むように設定できます。設定方法の詳細は、私も確かなことは把握していないので、省略しておきます。
perf_event_open システムコールを使って、カウンタをオープンします
perf_fd = perf_event_open(&attr, 0, -1, -1, 0);
これで準備ができました。あとは、このファイルディスクリプタに対してreadすると、カウンタ値が64bit整数として取得できます
read(perf_fd, &val0, sizeof(val0));
tsc0 = __rdtsc();
memcpy(data[0], data[1], sizeof(data[0]));
tsc1 = __rdtsc();
read(perf_fd, &val1, sizeof(val0));
/sys/devices/system/cpu/cpuX/cpufreq 経由で、cpu クロックを、1.2GHz に固定した状態でこのサンプルを動かすと、
CPU_UNHALTED_CLOCK=156467, tsc=432910
CPU_CLK_UNHALTED=156758, tsc=434044
CPU_CLK_UNHALTED=156291, tsc=433024
CPU_CLK_UNHALTED=155922, tsc=431834
CPU_CLK_UNHALTED=156310, tsc=432808
CPU_CLK_UNHALTED=156254, tsc=433172
クロックを3.4GHzに固定して動かすと、
CPU_CLK_UNHALTED=156798, tsc=153472
CPU_CLK_UNHALTED=166543, tsc=163060
CPU_CLK_UNHALTED=155522, tsc=152200
CPU_CLK_UNHALTED=156578, tsc=153352
CPU_CLK_UNHALTED=156372, tsc=152912
CPU_CLK_UNHALTED=156039, tsc=152680
となって、tscの値は、CPUクロックに依存せず、一定クロックでカウンタが増えていっているのに対し、CPU_CLK_UNHALTEDは、CPUクロックの変動にあわせて、カウントされる値も変動していることが確認できます。
(多少わかりづらいですが、CPUクロックを変動させると処理時間が変わるので、一定時間でクロックを刻んでいるtscのほうが、その処理時間の変動にあわせて、カウンタの差分が変動します)
perf_eventの問題点として、x86、Linux にかなり依存する、VM上では使えない可能性が高いなどの問題があります。
状況にあわせて計測方法を使いわけていくのがよいでしょう。
perf_eventインターフェースはCPU_CLK_UNHALTED以外にも色々な値を取得できます。機会があればそれについても紹介するかもしれません。
コンピュータビジョンセミナーvol.2 開催のお知らせ - ニュース一覧 - 株式会社フィックスターズ in Realizing Self-Driving Cars with General-Purpose Processors 日本語版
[…] バージョンアップに伴い、オンラインセミナーを開催します。 本セミナーでは、...
【Docker】NVIDIA SDK Managerでエラー無く環境構築する【Jetson】 | マサキノート in NVIDIA SDK Manager on Dockerで快適なJetsonライフ
[…] 参考:https://proc-cpuinfo.fixstars.com/2019/06/nvidia-sdk-manager-on-docker/ […]...
Windowsカーネルドライバを自作してWinDbgで解析してみる① - かえるのほんだな in Windowsデバイスドライバの基本動作を確認する (1)
[…] 参考:Windowsデバイスドライバの基本動作を確認する (1) - Fixstars Tech Blog /proc/cpuinfo ...
2021年版G検定チートシート | エビワークス in ニューラルネットの共通フォーマット対決! NNEF vs ONNX
[…] ONNX(オニキス):Open Neural Network Exchange formatフレームワーク間のモデル変換ツー...
YOSHIFUJI Naoki in CUDAデバイスメモリもスマートポインタで管理したい
ありがとうございます。別に型にこだわる必要がないので、ユニバーサル参照を受けるよ...