type t (* void *)

関数型言語や英語学習の事とか。

ARM: Rasberry pi 3でinstruction per cycleを測ってみたが……?

更新:
全部の実験で命令の総数を揃えました。
分かりにくくてすみません……

計算機が全くわからない。
俺たちは勘で計算している。 どなたかこの解釈について教えてほしいです。。。。。

雑にIPCとか測ったらどうなるかなーという願望が湧いたのでひたすら計測する。

doubleの加算

        .global main
        .type   main, %function
main:
        push {r3, lr}
        bl   test
        mov  r0, #0
        pop  {r3, pc}

        .global test
        .type   test, %function
test:
        mov r0, #0
        ldr r3, =70000000
loop:
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        faddd d0, d0, d1
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

だいたいやっていることは64bit浮動小数点の加算を10 * 7000000 = 7 * 10 ^ 8回やる。
パイプラインを止めるためにレジスタに依存があるに書く。
ハードウエアのことはわからないけど多分これはregister renaming出来ない気がする。

ちなみにldrは疑似命令で、定数のmovみたいなものに見えるが実際はどっかのアドレスからまさにldrになっているはず(objdumpすればホントに命令が見える)。
push, popも疑似命令でpush {r3, lr}ldmfd r13!,{r3,lr}の略記だった気がする。
r13レジスタはスタックポインタとして使われる汎用レジスタなのでまぁ必要な値を避難するのがpushpoplrに元いた場所のアドレスが書いてあってそれをpcに書き戻すのだからretの動きをする。

ラベルloop:で10回倍精度浮動小数点の加算を行い、cmpbneで1回条件分岐があるけど これくらいは分岐予測で殆どペナルティはないと考える。

実行命令数を7 * 10 ^ 8とちょっと変にしているのは狙ってのことで、 雑に/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freqを見てみると 700000と書いてあるため(ちなみに私のraspi 3のディストリはraspi版のcentos 7.3)。
この表記はKHzなので最大700MHzで動作する設定になっている。
あれ、Cortex A-53って最大1.2GHzで動作するんじゃなかったっけ。。。。。
あれ、これCortex A-7だと認識されている?

$ ls /sys/devices/armv7_cortex_a7/ # なんかある。。。。。

これをCPU稼働率MAXまでなって欲しいなーという気持ちで連続で動かして実行時間を測る。
ほんとはcpupowerとかで周波数を固定するべきな気がする。

[nomaddo@centos-rpi3 tmp]$ for i in `seq 1 100`; do time ./a.out ; done

real    0m4.199s
user    0m4.190s
sys     0m0.000s

real    0m4.022s
user    0m4.010s
sys     0m0.000s

real    0m4.022s
user    0m4.010s
sys     0m0.010s

real    0m4.022s
user    0m4.000s
sys     0m0.010s

real    0m4.022s
user    0m4.010s
sys     0m0.000s

real    0m4.022s
user    0m4.010s
sys     0m0.000s

real    0m4.011s
user    0m4.000s
sys     0m0.000s

real    0m4.010s
user    0m4.000s
sys     0m0.000s

real    0m4.010s
user    0m4.000s
sys     0m0.000s

real    0m4.010s
user    0m4.000s
sys     0m0.000s
...

おお?なんかきれいになった。計算の都合0.4秒かかったことにしよう!
全体の命令数の総数をサイクル数で割って、(7 * 10 ^ 8) / (7 * 10 ^ 8 * 0.4) = 2.5
おお? 2.5 命令/cycle? なんか早すぎる気もするんですが現代の計算機ってこんなもんなんですかね……?

命令間の依存がないとどうなる?1

気になったので依存がないモードもやってみる。

       .global main
        .type   main, %function
main:
        push {r3, lr}
        bl   test
        mov  r0, #0
        pop  {r3, pc}

        .global test
        .type   test, %function
test:
        mov r0, #0
        ldr r3, =70000000
loop:
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

faddの列に全く依存がないためきっと内部の計算資源をフルで使える気がする。

[nomaddo@centos-rpi3 tmp]$ for i in `seq 1 100`; do time ./a.out ; done

real    0m0.827s
user    0m0.820s
sys     0m0.000s

real    0m0.711s
user    0m0.700s
sys     0m0.000s

real    0m0.708s
user    0m0.700s
sys     0m0.000s

real    0m0.706s
user    0m0.700s
sys     0m0.000s

real    0m0.705s
user    0m0.700s
sys     0m0.000s

real    0m0.708s
user    0m0.700s
sys     0m0.000s

real    0m0.705s
user    0m0.700s
sys     0m0.000s
...

命令間の依存がないとどうなる?2

こんなすぐに1ループ回ると条件分岐の影響を強く受けそうだなと思ったので別バージョン。
演算総数は変わらないけど、1ループで20回倍精度の浮動小数点数の加算が動くようにする。

        .global main
        .type   main, %function
main:
        push {r3, lr}
        bl   test
        mov  r0, #0
        pop  {r3, pc}

        .global test
        .type   test, %function
test:
        mov r0, #0
        ldr r3, =35000000
loop:
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

実行してみる。楽しみだなー。

[nomaddo@centos-rpi3 tmp]$ for i in `seq 1 100`; do time ./a.out ; done

real    0m0.706s
user    0m0.710s
sys     0m0.000s

real    0m0.710s
user    0m0.700s
sys     0m0.000s

real    0m0.649s
user    0m0.640s
sys     0m0.000s

real    0m0.605s
user    0m0.600s
sys     0m0.000s

real    0m0.608s
user    0m0.600s
sys     0m0.000s

real    0m0.605s
user    0m0.600s
sys     0m0.000s

real    0m0.611s
user    0m0.600s
sys     0m0.000s

real    0m0.606s
user    0m0.600s
sys     0m0.000s

real    0m0.611s
user    0m0.600s
sys     0m0.000s

real    0m0.605s
user    0m0.600s
sys     0m0.000s

命令間の依存がないとどうなる?3

調子に乗って4展開してみる。

        .global main
        .type   main, %function
main:
        push {r3, lr}
        bl   test
        mov  r0, #0
        pop  {r3, pc}

        .global test
        .type   test, %function
test:
        mov r0, #0
        ldr r3, =17500000
loop:
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        faddd d1, d2, d3
        faddd d4, d5, d6
        faddd d7, d8, d9
        faddd d10, d11, d12
        faddd d13, d14, d15
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr
nomaddo@centos-rpi3 tmp]$ for i in `seq 1 100`; do time ./a.out ; done

real    0m0.648s
user    0m0.640s
sys     0m0.000s

real    0m0.622s
user    0m0.610s
sys     0m0.010s

real    0m0.558s
user    0m0.550s
sys     0m0.010s

real    0m0.557s
user    0m0.550s
sys     0m0.000s

real    0m0.556s
user    0m0.550s
sys     0m0.000s

real    0m0.559s
user    0m0.550s
sys     0m0.000s

real    0m0.561s
user    0m0.550s
sys     0m0.000s

real    0m0.561s
user    0m0.550s
sys     0m0.000s

real    0m0.555s
user    0m0.550s
sys     0m0.000s
...

おもったよりしっかり分岐のペナルティがあったのか????
なんかもう真率がたかいループだからてっきりほぼ100%くらい分岐予測に成功するのかと思っていた。

計算したは良いけどこれがあっているのか全くわからない感じでした。
色々仮説を立てて検証してみよう。。。。。。。