SIMDベクトル・行列ライブラリを作った

TL;DR

高水準なAPISIMD命令を使って小さなベクトル・行列計算するためのライブラリを作った。

github.com

名前は安直だが MAtrix と VEctor からとった。

数年前にもExpression Templateを使って似たようなものを書いたことはあったが、これは的を絞った分ちゃんと狙いが定まった感がある。

Motivation

サーベイ不足は自覚しているので、以下は雑な感想である。正直に言うと、理由の半分くらいは「面白そうだったからやってみたかった」だ。

AVXをサポートしているCPUを持っているなら、floatが8個まで同時に加算できる。これは便利だが、ごく普通に3次元ベクトルに対して演算子オーバーロードをするとAVX命令を使うかどうかはコンパイラ任せになる。

ここで絶対に8個同時に計算したい、という場合はあるだろう。というわけで、以下のような演算子をサポートした。

mave::vector<float, 3> v1(/*...*/), v2(/*...*/);
mave::vector<float, 3> w1(/*...*/), w2(/*...*/);
auto [u1, u2] = std::tie(v1, v2) + std::tie(w1, w2);

C++17の構造化束縛が使えない場合は、std::tieを使うと良い。

maveは3次元ベクトルのためのライブラリなので、長さを求めるなどの計算も同様に出来て、

// l1 = mave::length(v1 - w1);
auto [l1, l2] = mave::length(std::tie(v1, v2) - std::tie(w1, w2));

と書ける。maveでは、4つまでのtieをサポートしている。

これらの演算子は、対応するSIMD命令が存在しなかった場合はできる量に分けて実行する。なので、常に最大個数で書いておいても大丈夫だ(レジスタの量が足りなかったり、命令の順番が悪くてパイプラインの効率が悪くなる可能性はあるし、端数の処理で減速する可能性もある)。

よくある線形代数ライブラリの目標は大きな行列やベクトルの複雑な計算を高速にすることなので、こんなケースのサポートは後回しだろう(と思う)。 また、maveは小さなベクトルの扱いに特化しているので、簡単なlengthなどの関数を用意している。このあたりが汎用のSIMDライブラリと異なる点だ。まあ、汎用のゼロオーバーヘッドSIMDライブラリを作ってからその上にこれ実装すれば良いのでは、というのはあるが、まあそれは置いておいて。

仕組みの概要

中で何をしているかに関しては、意外と大変なことがいくつかあったので回を改めてちゃんと書きたいが、大雑把に言うと要するにtieによって作られるタプルのそれぞれについて演算子オーバーロードをしているわけだ。 クラスの外でもoperator+operator+=は実装できるので、特定の中身を持ったstd::tupleについて演算子オーバーロードできる。ここに注目して、上記のようなインターフェースを作っている。