RISC-V gcc assembly 比較

2019/04/26

RISC-V用のgccでコードを生成して比較する

crosstool-NG( https://crosstool-ng.github.io/ )を使うとクロスコンパイル用のgccが簡単にセットアップできるので、 32-bit RISC-V(RV32IMF)のコードを生成してテストした。

ソースコード

extern float mem[];
int main (int argc, char * argv[])
{
  volatile float s = 0.0;
  for(int i = 0; i < 4; i++) {
    s += mem[i] + mem[i]*mem[i];
  }
}

コンパイル方法

“-S"をつけてアセンブリコードを出力する。

~/x-tools/riscv32-unknown-elf/bin/riscv32-unknown-elf-gcc -S sum.c -o sum.s

実行した場合のサイクル数比較

最適化オプションを変えてコード生成した上で、シミュレータで実行にかかったサイクル数を計測した。 最後の"jalr"命令までのサイクル数は以下のようになった。生成された命令数もカウントした。

opt cycles ops
-O0 177 42
-O1 62 16
-O2 58 14
-O3 43 27

最適化オプションなし/"-O0"の場合

一見して長い。ひとつには、main関数の最初にレジスタの待避処理があり、 ループでもカウンタ変数(i)をスタック変数として使いレジスタを活用していない。 演習の説明で使うような教科書的なコード。

“-O1"の場合

明らかにコンパクトになった。カウンタ変数にはレジスタを使い無駄がない。

“-O2"の場合

“-O1"とほぼ同じ。fmad命令が生成されている。

“-O3"の場合

ループがアンローリングされている。単純に”-O2"をアンローリングしただけでなく、 配列データのロード最適化や命令の並び替えもおこなわれている。 ただ、浮動小数点用レジスタは豊富にあるはずなのに使わず、無駄なロードストアがある。