type t (* void *)

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

ARM: Cortex-A53の実験

計算機がわからない。
ので実験を2つほど。

追記:
ろんだくん@fetburnerから「fadddとaddは同時に実行されているだろう」という指摘をうけました。
確かにALUとFPUは独立している回路なのでやってできないことはない筈です。
分岐命令の実行時間が無視できなくなっているのではないかということです。
解釈が間違っている可能性がありますので近日中に見直します。

ちなみに分岐命令のクロックを図りました。多分分岐予測に成功するときで3クロックです。

gist.github.com

浮動小数点数演算と整数演算

実験1:まえがき

fadddレジスタ間に依存があるがある場合にどうなっているのか実行時間を計測する。
今回は余っているraspi-3を引っ張り出してきてちゃんと公式のrasbianにしたのでちゃんと/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freqの値が1200000を指してます。
また、すべての実験で1.2 * 10 ^ 7回まわしています(1.2GHzが1.2 * 10 ^ 8なため)。 測定方法は前と同じく数回連続で回してCPUをMAXの周波数まで上げさせます。

        .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, =120000000
loop:
        faddd d0, d0, d0
        faddd d0, d0, d0
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

=> 0.8 s

多分こんな感じ。

f:id:no_maddojp:20170531223936j:plain

いろいろ試した結果、fadddは4クロックかかると推測している(1サイクル分はwrite backのためかもしれない)。 これを示すために2つのfadddの間に整数演算を入れてみて、かかる時間をみてみる。
注意事項として整数演算は完全に2命令同時実行可能、らしい(interface 2016 11月号)。

        .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, =120000000
loop:
        faddd d0, d0, d0
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        faddd d0, d0, d0
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

=> 0.8s

多分想像するにパイプラインは以下のようになっていると思われる。
ループの間の演算待ちは分岐予測により真率ほぼ100%のジャンプなので無いとして、命令間のfadddは多分命令待ちで実行を待つ。
実験の結果、大体の整数演算は1クロックで実行できると推測している。

しかし、これを見るに、整数演算は依存関係があっても完全に2命令実行可能な感じがしてくる。 検証のため、addを2個目のfadddの前にもう一つ挿入してみる。

        .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, =120000000
loop:
        faddd d0, d0, d0
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        faddd d0, d0, d0
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

=> 0.9 s

おお。多分以下のような図で実行されているのではないか。

f:id:no_maddojp:20170531223923j:plain

addが挿入されたおかげで1サイクル分余計に時間がかかり、それが実行時間に反映されている。
じゃあ2命令同時実行可能だから、もう一命令入れても同じ結果になる気がする、、、が。

不思議な挙動

       .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, =120000000
loop:
        faddd d0, d0, d0
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        add r1, r1, #1
        faddd d0, d0, d0
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

さっきよりadd命令を1つ増やした。前と同じ前提であれば、実行時間は変わらないはずだ。

=> 1.0s

おお?あれ?どこかで1サイクル損している?
どこかで2命令同時実行じゃなくなっているのではないか。

浮動小数点数演算とパイプライン

多分おそらくfadddは2命令同時実行できる。
そうしないと色々実行が早すぎて説明がつかない。。。。。

        .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, =120000000
loop:
        faddd d0, d0, d0
        faddd d1, d1, d1
        faddd d2, d2, d2
        faddd d3, d3, d3
        faddd d4, d5, d6
        faddd d5, d5, d5
        faddd d0, d0, d0
        faddd d1, d1, d1
        faddd d2, d2, d2
        faddd d3, d3, d3
        faddd d4, d5, d6
        faddd d5, d5, d5
        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

=> 0.9 s

1ループで9サイクルかかっているため、0.9秒。
多分下のようなイメージ。

f:id:no_maddojp:20170531232251j:plain

1サイクルd0の演算損している。なので、これにfaddd d6, d6, d6faddd d7, d7, d7を追加してみる。

        .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, =120000000
loop:
        faddd d0, d0, d0
        faddd d1, d1, d1
        faddd d2, d2, d2
        faddd d3, d3, d3
        faddd d4, d5, d6
        faddd d5, d5, d5
        faddd d6, d6, d6
        faddd d7, d7, d7
        faddd d0, d0, d0
        faddd d1, d1, d1
        faddd d2, d2, d2
        faddd d3, d3, d3
        faddd d4, d5, d6
        faddd d5, d5, d5
        faddd d6, d6, d6
        faddd d7, d7, d7

        add r0, r0, #1
        cmp r0, r3
        bne loop
        bx  lr

=> 1.0 s

計算通り。これは多分以下のようなパイプラインの気持ち。

f:id:no_maddojp:20170531232855j:plain

やっとなんだか計算機がわかった気がする。