この記事は、RISC-Vの可能性を最大限に引き出し、異なるアーキテクチャ間でのコードの再利用性とパフォーマンス最適化に興味のある開発者に向けたものです。
Intel Intrinsicに関して
Intel Intrinsic(インテル イントリンシック)は、インテルのプロセッサにおけるSIMD(Single Instruction, Multiple Data)命令セットを直接活用するための、C/C++言語の拡張機能です。これらの命令セットには、SSE(Streaming SIMD Extensions)、AVX(Advanced Vector Extensions)、およびその他の特定のプロセッサ機能向けの命令が含まれます。インテル イントリンシックを使用することで、開発者は高レベル言語を使用しながらも、ハードウェアの特定の機能を直接制御し、データ処理のパフォーマンスを最適化することができます。
イントリンシックは関数のように使用され、特定の命令に対応するコードを生成しますが、通常の関数呼び出しのオーバーヘッドがありません。これにより、アプリケーションの特定部分での計算効率が大幅に向上し、並列データ処理、画像処理、数値解析など、高い計算性能を要求されるアプリケーションの開発に役立ちます。
例えば、AVX-512 イントリンシックを使用することで、開発者は512ビット幅のレジスタを直接操作して、一度に多数の浮動小数点数の演算を行うことが可能になります。これにより、科学計算や工学シミュレーションなどの計算集約型タスクの実行時間を短縮することができます。
インテル イントリンシックを効果的に使用するには、対象となるプロセッサのアーキテクチャと、使用可能な命令セットに関する深い理解が必要です。また、コードのポータビリティに影響を与える可能性があるため、使用する際には注意が必要です。
RISC-VアーキテクチャとVector Extensionの基本的な特徴とその革新性
RISC-VのVector Extensionは、複数のデータ要素に対して一度に演算を行うベクトル処理を可能にします。これにより、科学計算、画像処理、機械学習など、データ並列性が重要なアプリケーションでのパフォーマンスが大幅に向上します。
べクトル演算を効率的に行うことで、同じ計算をより少ない命令で実行できるため、エネルギー消費を削減できます。これは、モバイルデバイスや組み込みシステムなど、電力消費に制約がある環境で特に重要です。
x86のIntel Intrinsicのベクトル演算をRISC-Vにポートして、Vector Extentionでベクトル演算させる
「x86のIntel Intrinsicのベクトル演算をRISC-Vにポートして、Vector Extensionでベクトル演算させる」プロセスにおいて、C言語を用いた実装の簡単な説明は以下の通りです。
このプロセスでは、x86アーキテクチャ向けに設計されたIntel Intrinsicsを使用して記述されたベクトル演算コードを、RISC-VアーキテクチャがサポートするVector Extensionを活用して実行できるように変換します。具体的には、以下のステップで構成されます。
#include <stdio.h>
typedef double __m256d __attribute__ ((__vector_size__ (32), __may_alias__));
typedef double __v4df __attribute__ ((__vector_size__ (32)));
extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__,
__artificial__))
_mm256_add_pd (__m256d __A, __m256d __B)
{
return (__m256d) ((__v4df) __A + (__v4df) __B);
}
// _mm256_loadu_pd
extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__,
__artificial__))
_mm256_loadu_pd (double const *__P)
{
return *(__m256d*)__P;
}
__m256d test_mm256_add_pd(__m256d a, __m256d b) {
return _mm256_add_pd(a, b);
}
int main() {
double temp[4];
__m256d a, b;
for(int i = 0; i < 4; i++) {
scanf("%lf", &temp[i]);
}
a = _mm256_loadu_pd(temp);
for(int i = 0; i < 4; i++) {
scanf("%lf", &temp[i]);
}
b = _mm256_loadu_pd(temp);
__m256d c = test_mm256_add_pd(a, b);
printf("%lf %lf %lf %lf\n", c[0], c[1], c[2], c[3]);
return 0;
}
上記のC言語ソースコードは、Intel Intrinsicの命令の一つであるベクトルの加算演算について、x86でもRISC-Vでも動くようにするためのソースコードです。
・ x86環境では、gccを使ってコンパイルします。
GCCのバージョンは13移行推奨です。
RISC-Vでかつベクトル拡張を使える環境をMacOSで整える
以下のコマンドでriscv-toolchainと実行環境導入します。
$ brew tap riscv-software-src/riscv
$ brew install riscv-toolchain
$ brew install riscv-isa-sim riscv-pk
riscv-toolchainのクロスコンパイラのバージョンを確認します。
$ riscv64-unknown-elf-gcc -v
Using built-in specs.
COLLECT_GCC=riscv64-unknown-elf-gcc
COLLECT_LTO_WRAPPER=/opt/homebrew/Cellar/riscv-gnu-toolchain/main/libexec/gcc/riscv64-unknown-elf/13.2.0/lto-wrapper
Target: riscv64-unknown-elf
Configured with: /private/tmp/riscv-gnu-toolchain-20240204-71321-wl7zhv/gcc/configure --target=riscv64-unknown-elf --prefix=/opt/homebrew/Cellar/riscv-gnu-toolchain/main --disable-shared --disable-threads --enable-languages=c,c++ --with-pkgversion=gc891d8dc2 --with-system-zlib --enable-tls --with-newlib --with-sysroot=/opt/homebrew/Cellar/riscv-gnu-toolchain/main/riscv64-unknown-elf --with-native-system-header-dir=/include --disable-libmudflap --disable-libssp --disable-libquadmath --disable-libgomp --disable-nls --disable-tm-clone-registry --src=.././gcc --enable-multilib --with-abi=lp64d --with-arch=rv64imafdc --with-tune=rocket --with-isa-spec=20191213 'CFLAGS_FOR_TARGET=-Os -mcmodel=medany' 'CXXFLAGS_FOR_TARGET=-Os -mcmodel=medany'
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (gc891d8dc2)
さてC言語(akari.c)をコンパイルします。
$ riscv64-unknown-elf-gcc -march=rv64gcv -O2 akari.c -o akari
brewで導入したspikeとpkを使って仮想環境で実行します。
$ spike --isa=rv64gcv pk akari
bbl loader
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
6.000000 8.000000 10.000000 12.000000
出力結果: 6.000000 8.000000 10.000000 12.000000
が見事に表示されました。
x86でも試してみて、同じ結果が表示されることを確認してみてください。