コンピュータの進歩の方向性としては、微細化技術がわかりやすいので注目されやすいが、実は別の方向性も注目されつつある。それは具体的にはCISCからRISCへの移行である。
CISCとは、Complex Instruction Set Computerの略で、複雑命令セットコンピュータである。
CISCの代表として挙げられるのがIntel x86-64であるが、可変長命令であり、1979年に作られたオブジェクトコードが現代の最新のIntel x86-64プロセッサでも実行できる。それはx86-64の強みでもあるのだが、同時に弱みでもある。
CISCが開発された当初は、アセンブラでプログラミングされることが多く、アセンブリプログラムを容易にしたいという目的から、命令セットが次第に複雑化される傾向があった。CISCの命令セットには、例えば、データ構造へのアクセスを支援するための複雑なアドレッシング・モードや、レジスタを退避して、引数をスタック上に引き出す呼び出し命令が含まれる。(A.Vエイホ、「コンパイラ 第二版」)
コンパイラの最適化によって、よりシンプルな命令セットを使うことができるようになる、その結果できたものがRISC-VやARMなどのRISCプロセッサである。
RISCとは、Reduced Instruction Set Computerの略で、縮小命令セットコンピュータのことである。RISC-V、ARM、OpenPOWERなどがそれである。現在はスマホやタブレット、スパコンなどに使われることが多い。
プロセッサ設計において、後方互換性は神聖不可侵である。それはRISC-VなどのRISCでも同じであるが、RISC-Vは比較的新しいアーキテクチャである。背負っているものが少ない。
1979年移行に作られたオブジェクトコードがすべて実行できるということは、プロセッサの複雑性がどんどん増していくことを意味する。x86-64においては、内部でCISC命令をμOpと言われるRISC命令に変換しており、そのオーバーヘッドが存在する。x86-64を積んだコンピュータが熱くなりやすいのはそのせいである。
x86-64は現時点において、サーバやスパコンやPC市場において大きなシェアを握っている。x86-64のソフトウェア資産は膨大であり、世界はそれを手放すことが出来ない。
x86-64の非効率さによる電力などのロスは膨大である。ダイサイズは無駄に大きくなり、熱くなったCPUを冷却する装置なども必要となり、それも電力を消費する。それがデータセンターの収益性を圧迫し、地球温暖化を加速している。
では、僕たちはそれを放置するしかないのだろうか。そこで提案される方法が、Intel x86-64のIntrinsic関数を他のRISCなどにポートすることである。
今回提案する方法は、x86-64のバイナリコードを直接RISCのバイナリコードに変換する方法ではない。
x86-64で一般的に使用されているSIMD(Single Instruction Multiple Data)演算をC言語からアクセスするための関数がIntel x86-64のIntrinsic関数である。ここで重要なのは「Intel x86-64の」Intrinsic関数であり、RISCなどでは別のIntrinsic関数が存在する。
よって、Intel x86-64のIntrinsic関数が記述されたC言語のソースコードはIntel依存であり、RISCでは動かすことが出来ない。そして、そのIntel依存の膨大なソフトウェア資産があるため、非効率なx86-64から脱却することが出来ない。
ではどうすればいいか、 Intel x86-64 Intrinsic関数をRISCにポートすればいい。具体的には以下のようなラップ構造を取る。以下の例は、Intel x86-64からIBM Powerにポートしたものである。
extern __inline __m128d __attribute__((__gnu_inline__, __always_inline__,__artificial__))
_mm_add_pd (__m128d __A, __m128d __B)
{
return (__m128d) ((__v2df)__A + (__v2df)__B);
}
_mm_add_pdはIntel x86-64のIntrinsic関数である。それは比較的「ゆるっと」ポートしてあるもので、パフォーマンスを最適化しようとしたら更に複雑なソースコードになる可能性がある。
このようなラップ構造の利点は、本体のコードを改変せず、Intel Intrinsicをexternによって置き換えるだけでRISC-V対応ができることである。
では、もう一つの疑問が浮かび上がってくる
このポートしたソースコードは本当に正しいのだろうか。
2−3個試してみてもわからない。偶然結果が合致しただけかもしれない。
今構想しているのは、IntelからRISC-Vにポートしたソースコードの正しさを検証するテストフレームワークである。
例えば、上のソースコードにおいて、ランダムに生成した値をIntel IntrinsicとそれをRISC-Vにポートしたものの__Aと__Bにランダムに生成した同じ値を入れ、帰ってくる値がIntelとRISC-Vで同じかどうか検証する。
IntelとRISC-Vをネットワークで接続する必要はない。まずはIntelで、Intrinsic関数の入力値と戻り値のセットを大量に集めたファイルを作り、RISC-Vにファイルを移動させて、RISC-VのIntrinsic関数をポートしたものに入力して、入力値と戻り値がIntelで生成したものと同じであるかどうかをチェックする。
これには、__m128dの値をテキストに変える(シリアライズする)機構が必要である。なぜかというと、ランダムに生成したIntel Intrinsicに対する入力値と、その出力値のペアの膨大なセットをファイルに保存して、IntelからRISC-Vに移動などして、再びチェックを走らせて、Intel IntrinsicとそれをRISC-Vにポートしたものが等価であるかどうかチェックする必要があるからである。
最後にこれをテーマにした勉強会を毎週土曜日16時からやってます。無料ですので気軽にご参加ください。
「計算機最適化勉強会(オンライン) connpass」で検索!!
https://performanceoptimization.connpass.com/
参考文献
コンパイラ 第2版: 原理・技法・ツール (Information&Computing 38)
https://amzn.asia/d/4e115P2
Vector Intrinsics Porting Guide
https://openpowerfoundation.org/specifications/vectorintrinsicportingguide/