前回Coremarkの測定を行ったし、その前は各命令のレイテンシを測定した。 このとき、実際にプログラムのサイクル数を測定しているわけだが、これはどのようにして実現しているのだろう。
まず各命令のレイテンシ、スループット測定には、以下のようなサイクル取得ルーチンを使用している。
#define rdmcycle(x) { \ uint32_t lo, hi, hi2; \ __asm__ __volatile__ ("1:\n\t" \ "csrr %0, mcycleh\n\t" \ "csrr %1, mcycle\n\t" \ "csrr %2, mcycleh\n\t" \ "bne %0, %2, 1b\n\t" \ : "=r" (hi), "=r" (lo), "=r" (hi2)) ; \ *(x) = lo | ((uint64_t) hi << 32); \ }
これはつまり、
- 現在のmcyclehの値を取得し、
hi
に代入する。 - 現在のmcycleの値を取得し、
lo
に代入する。 - 現在のmcyclehの値を取得し、
hi2
に代入する。 - hiとhi2を比較し、不一致ならば1.から繰り返す。
これはmcyclehがオーバフローした場合を考慮したルーチンとなっている。
例えば、
1.を実行した時点で{mcycleh,mcycle}=0x0000_0010_ffff_ffff
2.を実行した時点で{mcycleh,mcycle}=0x0000_0011_0000_0000
となった場合、最後の計算で{mcycleh,mcycle}=0x0000_0011_ffff_ffff
となってしまい、大幅にサイクル数が狂ってしまう。
これを防ぐために、mcycleh
を2回ロードし、変化していないことを確認してからサイクル数を計算するという訳だ。
32bit版RISC-V (HiFive1) プロセッサでのサイクルカウント方法
計算方法としては難しいけれども、32bit版のRISC-Vでは上記ではそのまま計測できない。そこで、サイクルカウンタのHiレジスタとLoレジスタを別々に出力することにした。
#define rdmcycle(hi_cycle, lo_cycle) { \ uint32_t lo, hi, hi2; \ __asm__ __volatile__ ("1:\n\t" \ "csrr %0, mcycleh\n\t" \ "csrr %1, mcycle\n\t" \ "csrr %2, mcycleh\n\t" \ "bne %0, %2, 1b\n\t" \ : "=r" (hi), "=r" (lo), "=r" (hi2)) ; \ hi_cycle = hi; lo_cycle = lo; \ }
以下のようにして出力する。差分の計算とかは難しいけど。とりあえずprintf()
くらいは表示できるようになった。
uint32_t start_cycle[2], stop_cycle[2]; rdmcycle(start_cycle[1], start_cycle[0]); ... printf ("Correct = %d\n", correct); printf ("Time = %08x%08x - %08x%08x\n", stop_cycle[1], stop_cycle[0], start_cycle[1], start_cycle[0]);