RDTSC命令を使い、いまどきのCPUでクロックを測定してみる

「[最近のRDTSC命令は実際のCPUクロックを数えていない](http://www.02.246.ne.jp/~torutk/cxx/clock/cpucounter.html)」という記事を見つけて、本当にそんなことがあるのかと気になったので、確認してみることにした。

元記事にあった **Linux OS/GNU CコンパイラでのRDTSC命令** という場所を参考にしたのだが、そのままでは使えないようだし、どうもうまく動かなかった。
```c:rdtsc.h (original)
#ifndef RDTSC_H_
#define RDTSC_H_

inline unsigned long long rdtsc() {
    unsigned long long ret;
    __asm__ volatile ("rdtsc" : "=A" (ret));
    return ret;
}

#endif /* RDTSC_H_ */
```

```c:rdtsctest.c (original)
#include "rdtsc.h"
#include <stdio.h>

int measure_func()
{
    unsigned long long start = rdtsc();
    to_be_measured();
    unsigned long long stop = rdtsc();
    printf("measured time : %I64d [clock]\n", stop - start);

    return 0;
}
```
インラインアセンブラがうまく展開されないらしく、リンクに失敗する。何かが足りないらしい。
```c:rdtsc.h (modified)
#ifndef RDTSC_H_
#define RDTSC_H_

static inline unsigned long long rdtsc(void) {
        unsigned long long ret;
        __asm__ volatile ("rdtsc" : "=A" (ret));
        return ret;
}
#endif /* RDTSC_H_ */
```
static付けて、その他ちょこっとイジった程度だけど、エラーが出なくなったようだ。本当にコレでいいのか、イマイチ自信が無いが「動いてるのでヨシ！」
```c:rdtsctest.c (modified)
#include "rdtsc.h"
#include <stdio.h>
#include <inttypes.h>
#include <time.h>
#include <sys/time.h>

#define NANOSLEEP_TIME 5000000 //MEASURE TIME

void to_be_measured(void)
{
        struct timespec ts;

        ts.tv_sec = 0;
        ts.tv_nsec = NANOSLEEP_TIME;
        nanosleep(&ts, NULL);
}

unsigned long long measure_func(void)
{
        unsigned long long start, stop, diff;
        start = rdtsc();
        to_be_measured(); //WAIT
        stop = rdtsc();
        diff = stop - start;
        printf("measured time : %" PRIu64 "[clock] in ", diff);

        return diff;
}

int main(void)
{
        unsigned long long clockcount;
        struct timeval t0;
        struct timeval t1;

        gettimeofday(&t0, NULL);

        clockcount = measure_func();

        gettimeofday(&t1, NULL);

        if (t1.tv_usec < t0.tv_usec) {
                printf("%d.%d", t1.tv_sec - t0.tv_sec - 1, 1000000 + t1.tv_usec - t0.tv_usec);
        }
        else {
                printf("%d.%d", t1.tv_sec - t0.tv_sec, t1.tv_usec - t0.tv_usec);
        }
        printf("Sec.\n estimate clock speed = %" PRIu64 "Hz\n", (1000000000 / NANOSLEEP_TIME) * clockcount);
        return 0;
}

```
main()が存在しなかったので追加。to_be_measured()も中身が定義されていなかったので実装。中身にnanosleep()を使い、五百万ナノ秒(5mSec)のウェイトを入れてみた。

経過時間の測定には[LinuxCのサイトを参考に](http://linuxc.info/time/time6/)gettimeofday()を使用。
この関数は、マイクロ秒単位でしか計測できないのが注意点らしい。

measure_func()を符号なし64bit整数型でrdtscを読んだ差分を返すよう改造。
「[printfを使用して、符号なしlong long intをどのようにフォーマットしますか？](https://www.generacodice.com/jp/articolo/24227/How-do-you-format-an-unsigned-long-long-int-using-printf)」という記事を参考に、
unsigned long long を表示するprintf用マクロ(PRIu64)をフォーマットに追加。＜inttypes.h＞をincludeすると使えるらしい。

さっそく、先日組んだ Debian/GNU Linux(bookworm)機上でテストしてみた。
 ***Intel Core i5-12400F (on Native Linux)*** 
```console:test1_run
folding@home:~/src/rdtsc$ ./a.out
measured time : 12628852[clock] in 0.5093Sec.
 estimate clock speed = 2525770400Hz
```
正確さを詰めてないので誤差は結構あるだろうけど、実効クロックが2.52GHz辺りと算出できているので、一応それなりに動いてはいるようだ。

では次に、32bitのノートパソコンで測定してみよう。

***Intel(R) Core(TM)2 CPU @1.83GHz (on Native Linux)*** を用意した。
```console:test2_32run
oldmachine@ia32linux:~/src/rdtsc$ ./a.out 
measured time : 9318650[clock] in 0.5215Sec.
 estimate clock speed = 1863730000Hz
```
特にスリープも省電力も使ってないはずで、1.86GHzか。
確かにそれっぽいデータを吐くようだ。
64bit機でも32bit機でも、ソースに一切手を加える必要がないのは有り難い。

比較のため、このマシンを64bit Linuxで動かしてみる。

***Intel(R) Core(TM)2 CPU @1.83GHz (on Native Linux[AMD64])*** を用意した。
```console:test2_64run
oldmachine@amd64linux:~/src/rdtsc$ ./a.out 
measured time : 9391107[clock] in 0.5265Sec.
 estimate clock speed = 1878221400Hz
```
きちんと1.8GHzで動いてることになってるらしいことは分かった。

第八世代インテルCore i5 プロセッサでも走らせてみよう。
WSL上のUbuntuでコンパイルしてみると、派手に５つぐらいWarningが表示された。
ちゃんと動くのかな。long intやlong long intの扱いが合って無いっぽいが。
とりあえずリンクまで通ったようなので実行バイナリは生きているようだ。

***Intel(R) Core(TM) i5 CPU @2.30GHz (on WSL1)***
```console:test3_run
win10wsl@DESKTOP-Honyarara:~/src/rdtsc$ ./a.out
measured time : 11774144[clock] in 0.5379Sec.
 estimate clock speed = 2354828800Hz
```
なるほど、2.35GHzなら確かにそれっぽい周波数だと思う。
だが、WSLと同時にWindows上で動かしているCore Temp 1.17で実測した動作周波数は2.99～3GHzを指している。明らかに数値が違うんですけど？

では、素晴らしい性能で話題をさらったRyzenで動かすとどうなるのか。
こちらはWSLでもDebian入り。コンパイルエラーもウォーニングも出なかった。

***AMD Ryzen 5 3600X 6-Core Processor @3800.000MHz (on WSL1)***
```console:test4_run
win10wsl@DESKTOP-RYZEN:~/src/rdtsc$ ./a.out
measured time : 22547414[clock] in 0.6225Sec.
 estimate clock speed = 4509482800Hz
```
あれ、4.5GHzって速すぎるんじゃね？(WSL上なのでいろいろありそうだが)
Ryzen Masterで自動オーバークロックする設定だけど、モニタしてると3.8GHz台を上下していて、4GHzなんて凄い速さは出てないぞ。

もう１台、BIOS上げてから全然スピードが出なくなった DeskMini A300の測定結果を見てみよう。酷使しすぎたせいか、おそろしく速度がダウンしていて、最近は定格ですら動かない状態。
Ubuntu入りで、ソースをコンパイルするとWarningが報告される。DebianではWarningが出てこないところを見ると、どうもUbuntu特有の設定が関係しているようだ。

***AMD Ryzen 5 2400G 4-Core Processor @3600.000MHz (on WSL1)***
```console:test5_run
win10wsl@DESKTOP-RYZEN:~/src/rdtsc$ ./a.out
measured time : 21212730[clock] in 0.6286Sec.
 estimate clock speed = 4242546000Hz
```
君、Ryzen Master上だと定格の3.6GHzですら走らないレベルの2400G だよね？
4.25GHzで動作中って、どんだけサバ読んでるのよ。

***AMD Ryzen 5 2400G 4-Core Processor @3600.000MHz (on Debian/GNU [Native] Linux)***
```console:test6_run
AsrockMania@deskmini-a300:~/src/rdtsc$ ./rdtsctest 
measured time : 18227016[clock] in 0.5132Sec.
 estimate clock speed = 3645403200Hz
```
おや、同じマシンにDebianを入れてみたら、ちゃんと3.6GHz付近で動作してるという結果が上がってきた。
バックグラウンドで分散コンピューティングクライアントが稼働中なのでCPUはおそらくフルパワーで作動中。
一応 lscpu でCPUの状態をチェックしてみる。
```console:lscpu result
AsrockMania@deskmini-a300:~/src/rdtsc$ lscpu
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  Address sizes:          43 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   8
  On-line CPU(s) list:    0-7
Vendor ID:                AuthenticAMD
  Model name:             AMD Ryzen 5 2400G with Radeon Vega Graphics    
    CPU family:           23                                              
    Model:                17                                                
    Thread(s) per core:   2                                                   
    Core(s) per socket:   4                                                    
    Socket(s):            1                                                      
    Stepping:             0                                                       
    Frequency boost:      enabled                                                   
    CPU(s) scaling MHz:   98%                                                        
    CPU max MHz:          3600.0000                                                   
    CPU min MHz:          1600.0000                                                    
    BogoMIPS:             7200.12                                                        
    Flags:                fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat 
                          pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe
                          1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_ap
                          icid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 s
                          se4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy sv
                          m extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skin
                          it wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwait
                          x cpb hw_pstate ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rds
                          eed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 clzero irpe
                          rf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clea
                          n flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_
                          vmload vgif overflow_recov succor smca sev sev_es
Virtualization features:  
  Virtualization:         AMD-V
Caches (sum of all):      
  L1d:                    128 KiB (4 instances)
  L1i:                    256 KiB (4 instances)
  L2:                     2 MiB (4 instances)
  L3:                     4 MiB (1 instance)
NUMA:                     
  NUMA node(s):           1
  NUMA node0 CPU(s):      0-7
Vulnerabilities:          
  Gather data sampling:   Not affected
  Itlb multihit:          Not affected
  L1tf:                   Not affected
  Mds:                    Not affected
  Meltdown:               Not affected
  Mmio stale data:        Not affected
  Reg file data sampling: Not affected
  Retbleed:               Mitigation; untrained return thunk; SMT vulnerable
  Spec rstack overflow:   Mitigation; safe RET
  Spec store bypass:      Mitigation; Speculative Store Bypass disabled via prctl
  Spectre v1:             Mitigation; usercopy/swapgs barriers and __user pointer sanitizati
                          on
  Spectre v2:             Mitigation; Retpolines; IBPB conditional; STIBP disabled; RSB fill
                          ing; PBRSB-eIBRS Not affected; BHI Not affected
  Srbds:                  Not affected
  Tsx async abort:        Not affected
```
[CPU(s) scaling MHz:   98%] なので、ほぼフルスピードらしい。