目次: RISC-V
RISC-Vは最後発の命令セットだけあって、従来の命令セットで評判の悪かった部分は改善されている場合が多いです。しかし人の作るものですからミスや見落としはあります。
例としてわかりやすいのがRV64I命令セットの32bit unsignedと64bit unsigned加算処理です。下記のようなコードを書いたとします。
unsigned int __attribute__((noinline)) something(int n)
{
return n * n;
}
int test(unsigned int num)
{
unsigned long long sum = 0;
for (int i = 0; i < num; i++) {
sum += something(i); //32bit unsigned + 64bit unsigned
}
return sum;
}
下記のようにコンパイルします。RV64GCはRV64IMAFDCの略(M: Multiplication and Division, A: Atomic, F: Single-Precision Floating-Point, D: Double-Precision Floating-Point, C: Compressed)です。RV64I以外のMAFDCの各命令も出てきますが、話題と関係ないので気にしないでください。RV64GCが基本的な命令セットくらいの認識でOKです。
$ riscv64-unknown-elf-gcc -g -O2 -march=rv64gc -mabi=lp64d -c -o rv64gc.o a.c
逆アセンブルを見ると変なシフト命令(アドレス24, 26)が2つ出力されます。
000000000000001a <.L5>:
sum += something(i);
1a: 8522 mv a0,s0 # s0: i, s1: sum
1c: 00000097 auipc ra,0x0
20: 000080e7 jalr ra # call something()
24: 1502 slli a0,a0,0x20 # a0: something() の返り値
26: 9101 srli a0,a0,0x20 # shift x 2で上位32ビットを0埋め
for (int i = 0; i < num; i++) {
28: 2405 addiw s0,s0,1
sum += something(i);
2a: 94aa add s1,s1,a0 # shift x 2 + add
for (int i = 0; i < num; i++) {
2c: ff2417e3 bne s0,s2,1a <.L5>
RV64Iには32bit unsigned向けの加算命令がなく、32bit unsignedを64bit unsignedにゼロ拡張してから加算する必要があるためです。さらに悲しいことにゼロ拡張する命令もなく、32bit左シフト命令+32bit右論理シフト命令でゼロ拡張するヘボい処理になります。
シフト命令2つくらい何だというのか?ケチケチするなよ?という感覚が普通かもしれませんが、余計な命令が出ると特にローエンドのCPUでは性能への影響が無視できません。どうやらCoreMarkのような典型的なベンチマークにも影響が出ていたようで、
// GitHub: sifive/benchmark-coremark
// freedom-metal/core_portme.h
typedef signed short ee_s16;
typedef unsigned short ee_u16;
typedef signed int ee_s32;
typedef double ee_f32;
typedef unsigned char ee_u8;
typedef signed int ee_u32; //★★★u32なのに "signed" intになっている★★★
typedef signed long ee_u64; //★★★u64なのに "signed" longになっている★★★
#if __riscv_xlen == 32
typedef ee_u32 ee_ptr_int;
#else
typedef ee_u64 ee_ptr_int;
#endif
typedef signed int ee_size_t;
RISC-Vの盟主たるSiFiveすらも「unsigned型をsigned型にすりかえて性能を上げるぞい!」というCoreMarkハックを行っていた(該当箇所へのリンク)ほどです……。
当然RV64Iのまずい点はRISC-Vの方々も気づいており、B拡張(Bit-manipulation extensions)を追加したときに上記の問題は修正されました(RISC-V Bitmanipulation extension規格書へのリンク)。
B拡張はZba, Zbb, Zbc, Zbsの4つがあります。
この中のZba拡張にて32bit unsigned加算命令であるadd.uw命令が追加されました。他にも1, 2, 3bitシフト&加算命令なんかも追加されています。unsigned加算や1, 2, 3bitシフト&加算は配列の要素のアドレスを計算する際に頻出で、Address generation instructionsというグループ名にしたのでしょう。
Zba拡張を使うとどのように改善されるか確認します。
$ riscv64-unknown-elf-gcc -g -O2 -march=rv64gc_zba -mabi=lp64d -c -o rv64gcb.o a.c
逆アセンブルを見ると変なシフト命令は消滅し、新たにadd.uw命令が出力されていることが分かると思います。
000000000000001a <.L5>:
sum += something(i);
1a: 8522 mv a0,s0 # s0: i, s1: sum
1c: 00000097 auipc ra,0x0
20: 000080e7 jalr ra # call something()
for (int i = 0; i < num; i++) {
24: 2405 addiw s0,s0,1
sum += something(i);
26: 089504bb add.uw s1,a0,s1 # shift x 2は消滅、add.uwのみ
for (int i = 0; i < num; i++) {
2a: ff2418e3 bne s0,s2,1a <.L5>
めでたしめでたし。なんですけど、人によっては色々言いたいこともあると思います。Bit-manipulationとAddress generation全然関係ないぞ?とかね。
しかし冒頭にも書いたとおり、何事も最初から完璧なものはないです。命令セットが汚くなっていくのはRISC-Vが実用段階に入った証であり、むしろ良いことだと個人的には思います。
< | 2023 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | - | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | - | - | - | - | - |
合計:
本日: