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