目次: RISC-V - まとめリンク
以前(2022年 12月 22日の日記参照)の日記で CoreMark のスコアを測って表にしました。実は CoreMark は Ofast のみでは最速にはならず、コンパイルオプションをガチガチにチューンすると結構差が出ます。実際に NSITEXE NS31A の測定結果でお見せしたいと思います。
どうして NS31A かというと、非常にシンプルな CPU&自分の会社で作っているので素性が明確であるためです。複雑な CPU、中身の分からない CPU になればなるほど、総当たりでオプションの組み合わせを試す不毛な作業になりがちです。今回はそういう組み合わせ問題を解きたいわけじゃないんで、簡単な奴で行きます。
まずはベースとなる Ofast の結果です。実は O3 でも結果は同じです。
2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 18912 Total time (secs): 18.912000 Iterations/Sec : 58.164129 Iterations : 1100 Compiler version : GCC12.2.0 Compiler flags : -Ofast -gdwarf-4 -march=rv32im -mabi=ilp32 -mcmodel=medany Memory location : Please put data memory location here (e.g. code in flash, data on heap etc) seedcrc : 0xe9f5 [0]crclist : 0xe714 [0]crcmatrix : 0x1fd7 [0]crcstate : 0x8e3a [0]crcfinal : 0x33ff Correct operation validated. See README.md for run and reporting rules. CoreMark 1.0 : 58.164129 / GCC12.2.0 -Ofast -gdwarf-4 -march=rv32im -mabi=ilp32 -mcmodel=medany / Heap
動作周波数は 25MHz ですので、58.164129 / 25 = 2.326 CM/MHz です。
最適化の基本となる、ループアンローリング、インライン化(-funroll-all-loops, -finline-functions)を足します。
キャッシュラインが 32バイトなので、関数の先頭を 32バイト境界に配置します(-falign-functions=32)。関数先頭で命令キャッシュミスヒットが発生したときに、同じキャッシュラインに後続の命令(1ラインに 32 / 4 = 8命令)が載ります。後続の命令フェッチがキャッシュヒットすれば、最初のミスヒットを挽回できるだろうという目的です。
ジャンプやループの際に実行しない命令が中途半端にキャッシュに取り込まれないよう(= 利用効率の向上)、ジャンプやループの位置は 8バイト境界に配置します(-falign-jumps=8 -falign-loops=8)。これも 32バイト境界にすべきかと思いましたが、コード領域が散逸しすぎるためか逆に遅いです。
基本的に関数はインライン化した方が call, ret を省略、レジスタ共用など全体的に最適化できて速いです。しかし NS31A は命令キャッシュが小さめ(FPGA 向けコンフィグでは 16KB)なので、無差別に関数をインライン化すると命令キャッシュがあふれてキャッシュミスヒットが発生してしまい、逆に遅くなります。
従ってあまりにも大きな関数はインライン化しないように設定します(-finline-limit=300)。デフォルト値 600 の 1/2 にしています(※)。
2K performance run parameters for coremark. CoreMark Size : 666 Total ticks : 15819 Total time (secs): 15.819000 Iterations/Sec : 69.536633 Iterations : 1100 Compiler version : GCC12.2.0 Compiler flags : -Ofast -gdwarf-4 -march=rv32im -mabi=ilp32 -mcmodel=medany -funroll-all-loops -finline-functions -finline-limit=300 -falign-functions=32 -falign-jumps=8 -falign-loops=8 Memory location : Please put data memory location here (e.g. code in flash, data on heap etc) seedcrc : 0xe9f5 [0]crclist : 0xe714 [0]crcmatrix : 0x1fd7 [0]crcstate : 0x8e3a [0]crcfinal : 0x33ff Correct operation validated. See README.md for run and reporting rules. CoreMark 1.0 : 69.536633 / GCC12.2.0 -Ofast -gdwarf-4 -march=rv32im -mabi=ilp32 -mcmodel=medany -funroll-all-loops -finline-functions -finline-limit=300 -falign-functions=32 -falign-jumps=8 -falign-loops=8 / Heap
動作周波数は 25MHz ですので、69.536633 / 25 = 2.781 CM/MHz です。ハードウェアは何も変えていませんが、性能 1.2倍です。コンパイルオプションの威力恐るべし。
(※)この数値は GCC 内部で使う仮想命令のライン数らしく、300 が本当に適切か示すのは不可能です。マニュアルを見ると 1/2 や 1/4 に調整することが多いようなので、それに倣っています(参考: GCC のマニュアル)。
Twitter で見かけて面白かった問題、解法をメモしておきます。
100階建てのビルから卵を落とします。卵はある階よりも低ければ割れませんが、ある階よりも高いと割れます。卵を 2つ持っているとき、卵が何階で割れるか調べる方法を提示してください。そのとき卵を落とす最大の回数をできる限り小さくしてください。
という問題です。
1階から順に落とせば確実にわかりますが、最大の投擲回数が 100回(卵が割れる階数が 100F のとき)になり、最適な方法ではありません。
卵が 2個あることを利用し、1個目は複数階を一気に飛ばして(例 10F, 20F, 30F, ...)投擲します。例えば 10F 飛ばしで試すとすると 10F, 20F, 30F で割れず 40F で割れたら、2個目は「最後に割れなかった階の 1つ上の階」すなわち 31F から投擲します。このように試すともっと早く卵が割れる階数がわかります。
卵の投擲回数の最大値を計算すると、例えば 10F 飛ばしであれば 1個目が最大 10回(10F, 20F, 30F, ..., 90F, 100F)、2個目が最大で 9回(x1F 〜 x9F)なので、19回(卵が割れる階数が 99F のとき)です。100回と比べるとだいぶ少なくなりましたが、まだ無駄が残っています。
固定階数を飛ばす方法だと 1個目と 2個目の最大の投擲回数の和を見たとき、上の階になるほど悪化します。例えば、
1個目の投擲方法を変えて最初は 10F 飛ばし、次は 9F 飛ばし、その次は 8F 飛ばし、のようにすると投擲回数の最大値が悪化しないことに気づくと思います。
こんな表で示すとわかりやすいでしょうか。
1個目を投擲する階数 | 次に何階飛ばすか | 1個目の投擲回数 | 2個目の投擲開始階 | 2個目の最大の投擲回数 | 最大の投擲回数 |
---|---|---|---|---|---|
100 | 12 | 98 | 2 | 14 | |
97 | 3 | 11 | 94 | 3 | 14 |
93 | 4 | 10 | 89 | 4 | 14 |
88 | 5 | 9 | 83 | 5 | 14 |
82 | 6 | 8 | 76 | 6 | 14 |
75 | 7 | 7 | 68 | 7 | 14 |
67 | 8 | 6 | 59 | 8 | 14 |
58 | 9 | 5 | 49 | 9 | 14 |
48 | 10 | 4 | 38 | 10 | 14 |
37 | 11 | 3 | 26 | 11 | 14 |
25 | 12 | 2 | 13 | 12 | 14 |
12 | 13 | 1 | 1 | 11 | 12 |
1個目は 12F, 25F, 37F, 48F, 58F, 67F, 75F, 82F, 88F, 93F, 97F, 100F の順に投擲します。最大で 12回です。割れたら最後に成功した階の 1つ上の階から投擲すると、最大 14回で卵が割れる階数がわかります。
最大の回数になるケースは 12通りですが、一例として卵が割れる階数が 99F の場合を示します。12, 25, ..., 100(1個目が割れる), 98, 99(2個目が割れる)の 14回になります。
他には 1個目を投げる階数を下記のようにしても良いです。
いずれも投擲回数は最大 14回となります。
< | 2023 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 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 | - | - | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)