ARMの64bit命令セットと32bit命令セットの速度比較 strikes back

# この記事について

64bit 対応 ARM CPU で 32bit バイナリを動かすのがどれぐらいもったいないのかが知りたいという名目のマイクロベンチマーク。

で。
https://qiita.com/Nabetani/items/f5b685683e9c0180c279
の続編。

前述の記事では、 C++ であまり手間を掛けずに 64bit バイナリ作る方法がわからなかったので仕方なく
go で書いたんだけど、今回はやり方が分かったので g++ で。

# 環境とビルド

ハードウェアは Raspberry Pi 3B+。
乗っている CPU は Cortex-A53。
Cortex-A53 の命令セットは ARMv8-A。
OS は、Ubuntu 20.04.01 LTS(64bit)。

```
$ uname -a
Linux ubuntu 5.4.0-1015-raspi #15-Ubuntu SMP Fri Jul 10 05:34:24 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux
```

ビルド方法は下表の通り

|名前|コンパイラ|特別なオプション|補足|
|:--|:--|:--|:--|
|v7|arm-linux-gnueabihf-g++|-march=armv7+fp|32bit|
|v7a-neon2|arm-linux-gnueabihf-g++|-march=armv7-a+neon-vfpv4|32bit|
|v8|arm-linux-gnueabihf-g++|-march=armv8-a+simd|32bit|
|arm64|g++|-march=armv8-a+simd|64bit|
|arm64clang|clang++|-march=armv8-a+simd|64bit|

共通オプションは
`-static -O2`
など。

※ コメントでの [指摘](https://qiita.com/Nabetani/items/1e1b9f00197f7729f2ad#comment-91d428e7149db88ae72b) を受けて、 `-Ofast` を撤去して測りなおした。

各コンパイラは詳しくは以下の通り

```
$ arm-linux-gnueabihf-g++ --version
arm-linux-gnueabihf-g++ (Ubuntu 9.3.0-10ubuntu1) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ --version
g++ (Ubuntu 9.3.0-10ubuntu2) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang++ --version
clang version 10.0.0-4ubuntu1 
Target: aarch64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```

`-march` に指定できるCPU名が多彩すぎて、何を入れるのが良いのかよくわからなかった。

# 測定方法。

特定の型 (uint8 とか、 float とか) の値が4つ入っている構造体を用意した。
で、その構造体を受け取って構造体を返す無意味な計算を実施して、それに要する時間を測った。

無意味な計算は、加算・乗算・除算。
浮動小数点版には sin と cos も入れた。
計算の詳細は https://github.com/nabetani/armbench/blob/c49003d47ad849ed1649bd88d416c185527399e3/cpp/main.cpp を参照のこと。

とはいえ。int が 32bit である場合、 C/C++ には (16bit整数 × 16bit整数) を実施するための文法がないので、そういう命令があってもその命令に落ちるかどうかはよくわからない。
そういう命令があったらそれを使えばよさそうな計算にはしたけどね。

測定回数は 31回。中央値を代表として採用した。

# 結果

## uint8

こんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/a1b4b9b8-4af7-e959-03db-6bf1889b1a1b.png)

64bit かどうかではなく、 v7 か v8 かでぜんぜん違う。
なんかいい命令が増えたんだろうと思う。なんだろうね。

## uint16

こんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/bc73193f-c8d7-144d-d148-fd62d9d559d9.png)

uint8 と似た傾向。

## uint32

こんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/34208774-d0b5-ca43-240f-d21024f3b87c.png)

uint8 と似た傾向。

## uint64

64bit 命令セットが本領発揮することが期待される uint64 はこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/57216d1e-3072-e868-ad64-f081f293d9ce.png)

やはり 64bit 命令セットかどうかでぜんぜん違う。

## float

32bit 浮動小数点はこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/5492b8d4-8912-c0c4-1aa7-df955ff4ba02.png)

前回 go で試したときとは異なり、 64bit命令セットかどうかで差が出た。
前回の go になくて今回あるのは、単精度の三角関数なので、その辺りで差が出たのかも。

## double

倍精度浮動小数点はこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6854/da51b04e-c6a6-04ab-0539-87fd8ce7907d.png)

あんまり変わらないかな。

# まとめ

表でまとめるとこんな感じ。

|データ|雰囲気|
|:--|:--|
|uint8|v7 が遅く、v8 は 32bit でも 64bit でも速い|
|uint16|v7 が遅く、v8 は 32bit でも 64bit でも速い|
|uint32|v7 が遅く、v8 は 32bit でも 64bit でも速い|
|uint64|32bit が遅く、64bit が速い|
|float|32bit が少し遅く、64bit が少し速い|
|doulbe|あんまり変わらない|

単精度浮動小数点だと、32bit命令セットと 64bit命令セットで 若干差が見えた。
倍精度浮動小数点だと、どれも大差ない感じ。
ARMv8-A には、 ARMv7-A 以前の命令セットにはない良い命令があるのか、32bit までの整数演算 では v7チームと と v8チームで大きな差が出た。
32bit か 64bit かの違いでも差があるように見えるが、 v7チーム と v8チーム の差に比べたら大したことがない。


で。
go のビルドで ARM のバージョンを指定する際、 32bit の ARMv8 は指定できないので、もったいないと思う。

Raspberry Pi OS は `uname` すると `armv7l` となるので、やっぱりもったいないんじゃないかと思う。

# 補足

まあマイクロベンチマークなので、そういうこともあるよ、ぐらいで。