Article

2014年11月14日

近年の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を読むサンプルは以下になります。


https://bitbucket.org/fixstars/blog/src/cc0f605a427d1c29f751f80302abde588ee3a535/small_test/perf-clock.c?at=master

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以外にも色々な値を取得できます。機会があればそれについても紹介するかもしれません。

Tags

About Author

nakamura

Leave a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Recent Comments

Social Media