Invariant TSC を 使う

# はじめに
Intel CPU には 一定のクロックでカウントアップするカウンタがあります。
Invariant TSC (Time Stamp Counter) といいます。 この使い方を説明します。
sample code は MINGW64 の gcc で make します。

# Invariant TSC available Flag
CPUID instruction を使うことで Invariant TSC が 利用可能かどうかをチェックできます。
eax register に 0x80000007 を設定して cpuid 命令を実行します。edx の Bit 08 が そのフラグです。

## code 1
Invariant TSC available Flag を読み出す sample codeを示します。

```:makefile
CFLAGS=-I. -Wall -Werror -O2
INCS=
OBJS=test.o
LIBS=
TARGET=test

all: $(TARGET)

%.o: %.c $(INCS)
	$(CC) $(CFLAGS) -c -o $@ $<

%.o: %.s $(INCS)
	$(CC) $(CFLAGS) -c -o $@ $<

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)

clean:
	rm -rf $(TARGET) *.o
```

```:test.s
# rcx(1st) rdx(2nd) r8(3rd) r9(4th)
    .globl  main
main:
    subq    $40, %rsp
    call    __main
    
    # EDX Bit 08: Invariant TSC available if 1.
    movl    $0x80000007, %eax
    cpuid
    # printf
    leaq    s0, %rcx   # 1st
    movl    %edx, %edx # 2nd
    call    printf
    
    xorl    %eax, %eax
    addq    $40, %rsp
    ret
    .section .rodata
s0: .asciz "cpuid 0x80000007 edx : 0x%08X \n"
```

```bash:console
$ make && ./test.exe
cpuid 0x80000007 edx : 0x00000100
```

# RDTSCP instruction
Invariant TSC は 64 bit 符号なし整数です。RDTSCP instruction で 読み出せます。

## code 2
ENTER key 押下時 に TSC を読み出して 表示する codeを示します。
一定間隔で ENTER key を押下することで TSC の Frequency を推定できます。

```:test.c
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

static inline uint64_t RDTSCP();
static void print_tsc();

int main(int argc, char* argv[])
{
  while(1) {
    char buf[4];
    print_tsc();
    printf("press ENTER key.");
    fflush(stdout);
    fgets(buf, 4, stdin);
  }
  
  return 0;
}

static inline uint64_t RDTSCP()
{
  uint32_t hi, lo, aux;
  __asm__ volatile("rdtscp" : "=a" (lo), "=d" (hi), "=c" (aux));
  return ((uint64_t)hi << 32) | lo;
}

static void print_tsc()
{
  #define MAXU64 (0xFFFFFFFFFFFFFFFF)
  static uint64_t prev = MAXU64;
  uint64_t tsc, diff = 0;
  
  tsc = RDTSCP();
  if (prev != MAXU64) {
    diff = tsc - prev;
    printf("tsc=%" PRIu64 " diff=%" PRIu64 "\n", tsc, diff);
  } else {
    printf("tsc=%" PRIu64 "\n", tsc);
  }
  prev = tsc;
}
```

腕時計を見ながら 10秒間隔でENTER keyを押下してみます。
TSC の差分は22*10^9です。秒に変換すると2.2GHzです。
実行した CPU は Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz です。

```bash:console
$ make && ./test.exe
tsc=141732173016637
press ENTER key.
tsc=141752243551743 diff=20070535106
press ENTER key.
tsc=141774064869318 diff=21821317575
press ENTER key.
tsc=141796080991589 diff=22016122271
press ENTER key.
```

# Invariant TSC Frequency
正しいInvariant TSC Frequency を取得するには Machine specific registers(MSRs)を読み出す必要があります。
MSRsを読み出すには特権モードである必要がある為、少々敷居が高いです。
そのため code 2 では 一定間隔 の Invariant TSC 計測をすることで Frequencyを推定しました。

0xCE 15:8 bit の **Maximum Non-Turbo Ratio** に 100MHz を掛けたものが Invariant TSC Frequencyです。

>
0xCE 15:8 bit
Maximum Non-Turbo Ratio (R/O)
The is the ratio of the frequency that invariant TSC runs at.
Frequency = ratio * 100 MHz.
>

## Windows
CPU-Z で MSRs を読み出せます。Save Report as .TXT を選択してファイルに保存します。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/84205/f819ae4a-6f10-cbb8-2a5f-a9bd792b95f6.png)

テキスト内に次の行があります。

> 	Max non-turbo ratio	22x

よってこのCPUの Invariant TSC Frequency は 22x100MHz = 2.2GHz であることが分かりました。

## Linux
Linuxではrdmsrを使ってMSRsを読み出します。

```bash:console
$ cat /proc/cpuinfo | grep "model name"
model name      : Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
$ sudo apt-get install msr-tools
$ sudo modprobe msr
$ sudo rdmsr 0xce
80838f1012200
```

0x22 = 34です。
よってこのCPUの Invariant TSC Frequency は 34x100MHz = 3.4GHz であることが分かりました。