現在のデータセンターにおいての Intel Xeon と ARM のシェアは、それぞれ50%ほどである。Intelより ARM のほうがコストやパフォーマンスで勝っているが(ARM は Intel に比べて総コストが40%ほど安い)、Intel が使われている理由はソフトウェア互換性による。
主要な Linux ディストリビューションやプログラミング言語は ARM に対応しているが、100%ARM に対応できない理由の一つが、Intel Intrinsic 関数による(SSE、AVX、AVX-512 など)アーキテクチャ依存コードの存在である。
Intel の Intrinsic 関数は x86 の SIMD 命令を C/C++側から直接叩くためのものである。ARM には独自のIntrinsic 関数が存在する。(NEON や、最新の SVE/SVE2)これらはレジスタの幅も、用意されている命令の挙動も根本から異なる。)自動最適化(オートベクトライズ)には限界があり、高度に最適化された HPC(高性能計算)系のライブラリやデータベースのコアエンジン、マルチメディアコーデックなどは、コンパイラの自動最適化では到達できない極限のパフォーマンスを出すために、エンジニアが手動で Intel Intrinsic を記述しているケースがほとんどである。
ここで提案される手法が、Intel Intrinsic 関数を ARM で実行するために、特殊なラッパー構造を作ることである。
個人的にIntelからOpenPOWERまたはRISC-Vへポートする研究をしてきたが、同じ方法がIntelからARMへポートするのに使えると思う。
IntelのIntrinsic関数からOpenPOWERにポートするためには以下のようなラッパー構造を取る。
/* Perform the respective operation on the four SPFP values in A and B. */
extern __inline __m128 __attribute__((__gnu_inline__, __always_inline__,
__artificial__))
_mm_add_ps (__m128 __A, __m128 __B)
{
return (__m128) ((__v4sf)__A + (__v4sf)__B);
}
上のサイトによると、IntelのIntrinsic関数をARMにポートするためには以下のようなラッパー構造を取る。
inline __attribute__((always_inline)) __m128 _mm_add_ps(__m128 a, __m128 b) {
return vaddq_f32(a, b);
}
vaddq_f32はARM NeonのIntrinsic関数である。
また、sse2neonというオープンソースのライブラリでは以下のようなポートを行っている。
// Add packed single-precision (32-bit) floating-point elements in a and b, and
// store the results in dst.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_ps
FORCE_INLINE __m128 _mm_add_ps(__m128 a, __m128 b)
{
return vreinterpretq_m128_f32(
vaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)));
}
vreinterpretq_f32_m128とvreinterpretq_m128_f32が加わっているが、これは型変換用のイントリンシック関数である。この変換のコストはゼロである。中身のビット列は変えず、型の扱いを変えてほしいとコンパイラに知らせるだけのものだからである。新たに命令は生成されていない。
IntelのIntrinsic関数をARMにポートするのにも、正確性を確認するToukaは必要になるだろう。