Upload
akira-takahashi
View
11.346
Download
3
Embed Size (px)
DESCRIPTION
BoostCon 2011でのJoel Falcou, Mathias Gaunardによる発表「Practical SIMD acceleration with Boost.SIMD」の日本語訳。
Citation preview
Mathias Gaunard Joël Falcou Jean-Thierry Lapresté
MetaScale
Boostcon 2011
翻訳 : 高橋 晶([email protected])
Boost.SIMDによる実用的な SIMDアクセラレーション
コンテキスト
• 昨年、我々はハイパフォーマンスな数値計算のための、MatlabライクなProtoベースのライブラリであるNT2のプレゼンテーションを行った
• Boost.SIMDは、SIMDサブコンポーネント抽出のライブラリ
• GSoCプロジェクトが、レビュー準備のために今年の夏に行われる予定
• ここでは、どんな提案をするのかについて話す
コンテキスト
NT2からBoost.SIMDへ
SIMD
• ひとつの命令、複数のレジスタ
• ひとつのレジスタのNxT要素に適用された操作
• 通常のALU/FPUよりN倍速い
SIMDとは何か?
原則
SIMDの抽象化
• AltiVec 128-bit int8, int16, int32, int64, float
• Cell SPU 128-bit int8, int16, int32, int64, float, double
SIMDの抽象化はなぜ必要なのか?
PowerPCファミリー • MMX 64-bit float, double • SSE 128-bit float • SSE2 128-bit int8, int16, • int32, int64, double • SSE3 • SSSE3 • SSE4a (AMD only) • SSE4.1 • SSE4.2 • AVX 256-bit float, double • FMA4 (AMD only) • XOP (AMD only) • FMA3
x86ファミリー
• VFP 64-bit float, double
• NEON 64-bit and 128-bit float, int8, int16, int32, int64
ARMファミリー
明示的なSIMD並列化
• 自動ベクトル化(auto vectorization)は以下のような場合にだけ起こることができる:
– (何者かの代わりに)メモリを扱う者が決まっている
– コードは本来ベクトル化が可能である
• コンパイルされた関数はベクトル化されない(libmなど)
• コンパイラには、それが何をベクトル化することができるか判断できるくらいの静的な情報がいつでもあるわけではない
• ベクトル化のための設計は、ヒューマンプロセスである
コンパイラはなぜそれをしないのか?
コンパイラはとても賢いだけ
• 明確にSIMD並列性を宣言するのは、あなたのコードをベクトル化させる最も良い方法である
• このプレゼンテーションでデモンストレーションを行う
結論
Talk Layout
1. はじめに
2. インタフェース
3. SIMDとSTL
4. SIMD仕様イディオム
5. 結論
SIMD手書き
__m128i a, b, c, result ;
result = _mm_mul_epi32 (a, _mm_add_epi32 (b, c));
手書きしてみる
32ビット整数のベクトルでa * b + cする : SSE
__vector int a, b, c, result ; result = vec_cts ( vec_madd ( vec_ctf (a ,0) , vec_ctf (b ,0) , vec_ctf (c ,0) ) ,0);
32ビット整数のベクトルでa * b + cする : Altivec
パック
パック抽象化
pack<T, N> 型TのN要素をパックするSIMDレジスタ
pack<T> 利用可能な最適なNを自動的に使用する
• TとT以外のパックという例外操作を除いて、それはTのように振舞う。
simd::pack<T>
• Tは基本的な算術型でなければならない。たとえば:(un)signed char, (unsigned) short, (unsigned) int, (unsigned) long, (unsigned) long long, float or double - not bool.
• Nは2のべき乗でなければならない
制約
プリミティブ
パック抽象化
• オーバーロード可能な全ての演算子が利用できる
• pack<T> × pack<T>操作だけでなくpack<T> × Tも可能
• 型の強制と昇格の無効 uint8_t(255) + uint8_t(1)はint(256)ではなくuint_t(0)となる
演算子
• ==, !=, <, <=, >=は、語彙比較(lexical comparison)を行う
• eq, neq, lt, gt, le, ge関数は、boolのpackを返す
比較
• ReadOnlyRandomAccessFusionSequenceとReadOnlyRandomAccessRangeの両方をモデル化する
• at_c<i>(p)もしくはp[i]をアクセスに使用できる。i番目の要素へのアクセスは便利だが遅い(at_cは速い)
その他のプロパティ
プリミティブ
pack API
pack<T, N>からT*、もしくはその逆にload/storeを行うには、メモリはsizeof(T)*Nにアライメントされていなければならない。エラー時の振る舞いは未定義
メモリアクセス
Examples
プリミティブ
pack API
pack<T, N>からT*、もしくはその逆にload/storeを行うには、メモリはsizeof(T)*Nにアライメントされていなければならない。エラー時の振る舞いは未定義
メモリアクセス
load< pack<T, N> >(p, i)はp + i*Nアライメントされたアドレスからloadされる
例
プリミティブ
pack API
pack<T, N>からT*、もしくはその逆にload/storeを行うには、メモリはsizeof(T)*Nにアライメントされていなければならない。エラー時の振る舞いは未定義
メモリアクセス
load< pack<T, N>, Offset>(p, i)はp + i*N + Offsetアライメントされたアドレスからloadされる 。p + iはアライメントされていなければならない。
例
プリミティブ
pack API
pack<T, N>からT*、もしくはその逆にload/storeを行うには、メモリはsizeof(T)*Nにアライメントされていなければならない。エラー時の振る舞いは未定義
メモリアクセス
store(p, i, pk)はp + i*Nアライメントされたアドレスでpkにstoreする
例
プリミティブ
proto entityとしてのpack
• ほとんどのSIMD ISAはfused操作を持っている(FMA, etc...)
• 簡単なコードを書いて最も良いパフォーマンスを得たい
• 遅延評価が必要 : protoに助けてもらおう!
論拠
• 全ての式や関数が、変換演算子で評価されるテンプレート式を生成する
• a * b + cはfma(a, b, c)にマッピングされる a + b * cはfma(b, c, a)にマッピングされる !(a < b)はis_nle(a, b)にマッピングされる
• 拡張のための最適化システムは開かれている
利点
プリミティブ
他の算術、ビット、ieee演算と述語
• saturated arithmetic
• float/int変換
• round, floor, ceil, trunk
• sqrt, hypot
• average
• random
• min/max
• rounded division and remainder
算術演算
• select
• andnoot, ornot
• popcnt
• ffs
• ror, rol
• rshr, rshl
• twopower
ビット演算
• ilogb, frexp
• ldexp
• next/prev
• ulpldist
IEEE
• ゼロとの比較
• 比較の否定
• is_unord, is_nan, is_invalid
• is_odd, is_even,
• majority
述語
プリミティブ
ReductionとSWAR演算
• any, all
• nbtree
• minimum/maximum, posmin/posmax
• sum
• product, dot product
Reduction
• group / split
• slatetd reduction
• cumsum
• sort
SWAR
拡張ポイント
native<T, X> : archのTに関するSIMDレジスタ。X
• packと似ているが、全ての演算と関数の戻り値がExpression Templateを返すわけではない。
• Xは命令ではなく使用可能な型の登録。SSEの全てのバリエーションのタグだけを指定する。
• ライブラリを拡張するために使用されなければならないのはインタフェースである。
セマンティクス
native<float, tag::sse_> は __m128 をラップする
native<uint8_t, tag::sse_> は __m128i をラップする
native<double, tag::avx_> は __m256d をラップする
native<double, tag::altivec_> は __vector float をラップする
例
拡張ポイント
native<T, X> : archのTに関するSIMDレジスタ。X
• tag::none_<N>は、Nバイトサイズのレジスタによる、ソフトウェアエミュレートされたSIMDアーキテクチャ
• 満足のいくSIMDアーキテクチャが見つからない場合にフォールバックとして使用する
• これのおかげで、コードのデグレードが汎用的なまま
• SIMDが見つからない場合のデフォルトのネイティブ型 : native<T, tag::none_<8> >
ソフトウェアフォールバック
RGBをグレースケールにする
RGBをグレースケールにする
float const *red, *green, *blue;
float* result;
for (std::size_t i = 0; i != height * width ; ++i)
result[i] = 0.3f * red[i] + 0.59f * green[i] + 0.11f * blue[i];
スカラバージョン
std::size_t N = meta::cardinal_of<pack<float> >::value;
for (std::size_t i = 0; i != height*width/N; ++i)
{
pack<float> r = load< pack<float> >(red, i);
pack<float> g = load< pack<float> >(green, i);
pack<float> b = load< pack<float> >(blue, i);
pack<float> res = 0.3f * r + 0.59f * g + 0.11f * b;
store(res, result, i);
}
SIMDバージョン
プリミティブ
簡単だが、どうすれば…
• …RGB or RGBAが混在していたらどうするか?
• …floatではなく8ビット整数だったら?
複雑に聞こえるものは、あとで紹介する。
Talk Layout
1. はじめに
2. インタフェース
3. SIMDとSTL
4. SIMD仕様イディオム
5. 結論
論拠
演算 vs データ
• SIMD演算はデータ上で動作する必要がある
• 通常のアプローチでは、ユーザーに規定されたコンテナを強制する
• これは十分にジェネリックではない
どこで/どのようにデータを格納するか
• SIMD準拠アロケータ
• SIMDのRangeとイテレータ : CountiguousRange
• 標準アルゴリズムのサブセットを操作するためにSIMDクラスをアダプトする
より良いアプローチ
論拠
演算 vs データ
• SIMD演算はデータ上で動作する必要がある
• 通常のアプローチでは、ユーザーに規定されたコンテナを強制する
• これは十分にジェネリックではない
どこで/どのようにデータを格納するか
• SIMD準拠アロケータ
• SIMDのRangeとイテレータ : CountiguousRange
• 標準アルゴリズムのサブセットを操作するためにSIMDクラスをアダプトする
より良いアプローチ
SIMDアロケータ
SIMDアロケータ
• SIMDに準拠した方法でメモリを処理するコンテナを許可する
• メモリのアライメントをハンドル
• メモリのパディングをハンドル
論拠
std::vector<float, simd::allocator<float> > v(173);
assert( simd::is_aligned(&v[0]) );
例
SIMDイテレータとRange
RangeからSIMD Rangeへ
• Boost.SIMDは、simd::begin()/simd::end()を提供する
• SIMDイテレータはpackを返して回るイテレータ
• regular rangeをとり、SIMD上でイテレートする
イテレータインタフェース
Examples
SIMDイテレータとRange
RangeからSIMD Rangeへ
• Boost.SIMDは、simd::begin()/simd::end()を提供する
• SIMDイテレータはpackを返して回るイテレータ
• regular rangeをとり、SIMD上でイテレートする
イテレータインタフェース
例
SIMDイテレータとRange
RangeからSIMD Rangeへ
• Boost.SIMDは、simd::begin()/simd::end()を提供する
• SIMDイテレータはpackを返して回るイテレータ
• regular rangeをとり、SIMD上でイテレートする
イテレータインタフェース
例
std::vector<float, simd::allocator<float> > v(1024);
pack<float> x,z;
x = std::accumulate( simd::begin(v.begin())
, simd::end(v.end())
, z
);
SIMDイテレータとRange
RangeからSIMD Rangeへ
• Boost.SIMDは、simd::begin()/simd::end()を提供する
• SIMDイテレータはpackを返して回るイテレータ
• regular rangeをとり、SIMD上でイテレートする
イテレータインタフェース
例
std::vector<float, simd::allocator<float> > v(1024);
pack<float> x,z;
x = boost::accumulate( simd::range(v), z );
SIMDイテレータとRange
RangeからSIMD Rangeへ
• nativeとpackは、begin()/end()を提供する
• 標準アルゴリズムを直接使用可能
• Boost.Rangeアルゴリズムを直接使用可能
イテレータインタフェース
例
pack<float> x(1, 2, 3, 4);
float k = std::accumulate(x.begin() x.end(), 0.f );
SIMDイテレータとRange
RangeとしてのSIMD値
全てをまとめる
std::vector<float, simd::allocator<float> > v(1024);
pack<float> x, z;
float r;
x = boost::accumulate(simd::range(v), z);
r = std::accumulate(x.begin(), x.end(), 0.f);
SIMDイテレータとRange
RangeとしてのSIMD値
全てをまとめる – より良いバージョン
std::vector<float, simd::allocator<float> > v(1024);
float r;
r = sum(accumulate(simd::range(v), pack<float>()));
SIMDイテレータとRange
RangeとしてのSIMD値
doubleでのstd::accumulateの高速化
SIMDイテレータとRange
RangeとしてのSIMD値
floatでのstd::accumulateの高速化
SIMDイテレータとRange
SIMD RangeとジェネリックなSIMD/スカラーコード
RGB2Greyに戻る
template <class RangeIn, class RangeOut> inline void
rgb2grey( RangeOut result, RangeIn red, RangeIn green, RangeIn blue )
{
typedef typename RangeIn::iterator in_iterator;
typedef typename RangeOut::iterator iterator;
typedef typename iterator_value< iterator >::type type;
iterator br = result.begin(), er = result.end();
in_iterator r = red.begin();
in_iterator g = green.begin();
in_iterator b = blue.begin();
while ( br != er )
{
type rv = load< type >(r, 0);
type gv = load< type >(g, 0);
type bv = load< type >(b, 0);
type res = 0.3f * rv + 0.59f * gv + 0.11f * bv;
store(res, br, 0);
br++; r++; g++; b++;
}
}
SIMDイテレータとRange
何が足りないか
• ほとんどの標準アルゴリズムが、一息で実行できるように特殊化されるべきだ
• simd(r)のようなRangeアダプタができるだろうか?
• load<T, N>を使用したshifted Rangeのサポート
統合されたSIMDサポート
• SIMD find?
• SIMD sort?
• copyのような高速化?
いくつかのSIMDによる頭の体操
Talk Layout
1. はじめに
2. インタフェース
3. SIMDとSTL
4. SIMD仕様イディオム
5. 結論
条件ハンドリング
SIMDでの真理値
問題
pack<float> x(1 ,2 ,3 ,4);
pack<float> c(2.5);
cout << lt(x,c) << endl;
条件ハンドリング
SIMDでの真理値
問題
pack<float> x(1 ,2 ,3 ,4);
pack<float> c(2.5);
cout << lt(x,c) << endl;
(( Nan Nan 0 0 ))
条件ハンドリング
SIMDでの真理値
問題
pack<float> x(1 ,2 ,3 ,4);
pack<float> c(2.5);
cout << lt(x,c) << endl;
(( Nan Nan 0 0 ))
解決策
Tに適切なtrue値w/rを返すTrue<T>()
Tに適切なfalse値w/rを返すFalse<T>()
条件ハンドリング
SIMDでの条件式
例
// スカラーコード
if( x > 4 )
y = 2*x;
else
z = 1.f/x;
// SIMD code
// ???
条件ハンドリング
SIMDでの条件式
例
// スカラーコード
if( x > 4 )
y = 2*x;
else
z = 1.f/x;
// SIMD code
y = where( gt(x, 4), 2*x, y);
z = where( gt(x, 4), z, 1.f/x);
シフトした読み込み
動機
シフトした読み込み
着々と進んでいる…
シフトした読み込み
ベクトルの解決策
シフトした読み込み
ベクトルの解決策
SIMD/スカラーバージョン
template < class RangeIn, class RangeOut >
inline void average( RangeOut result, RangeIn input )
{
typedef typename RangeIn::iterator in_iterator;
typedef typename RangeOut::iterator iterator;
typedef typename iterator_value< iterator >::type type;
iterator br = result.begin(), er = result.end();
in_iterator data = input.begin();
br++; er--;
while ( br != er )
{
type xm1 = load<type, -1>(data, i);
type x = load<type>(data, i);
type xp1 = load<type, +1>(data, i);
store(res, i, 0) = 1.f/3 * (xm1 + x + xp1);
}
}
昇格と飽和(Promotion and Saturation)
RGB2Greyへ戻る
8ビットRGB
static const std::size_t N = meta::cardinal_of< pack<uint8_t> >::value;
for (std::size_t i = 0; i != height*width/N; ++i)
{
pack<uint8_t> r = load< pack<uint8_t> >(red, i);
pack<uint8_t> g = load< pack<uint8_t> >(green, i);
pack<uint8_t> b = load< pack<uint8_t> >(blue, i);
pack<uint8_t> res = uint8_t(77) * r / uint8_t(255) + uint8_t(150)
* g / uint8_t(255) + uint8_t(28) * b / uint8_t(255);
store(res, result, i);
}
昇格と飽和(Promotion and Saturation)
RGB2Greyへ戻る
packの昇格
uint16_t r_coeff = 77;
uint16_t g_coeff = 150;
uint16_t b_coeff = 28;
uint16_t div_coeff = 255;
pack<uint16_t> r1, r2, g1, g2, b1, b2;
tie(r1, r2) = split(r);
tie(g1, g2) = split(g);
tie(b1, b2) = split(b);
pack<uint16_t> res1 = (r_coeff * r1 + g_coeff * g1 + b_coeff * b1) / div_coeff;
pack<uint16_t> res2 = (r_coeff * r2 + g_coeff * g2 + b_coeff * b2) / div_coeff;
pack<uint8_t> res = group(res1, res2);
Talk Layout
1. はじめに
2. インタフェース
3. SIMDとSTL
4. SIMD仕様イディオム
5. 結論
Boost.SIMDの概要
proto entityとしてのpack
• SIMDプログラミングに、使いやすい状態をもたらす
• もしboost.atomicがあったら、boost.simdをどうするか?
• C++の残りのすばらしいものを使って、さらに魅力的にする
我々の目標
• NT2で学んだことの活用する
• パフォーマンス用語のいくつかの影響を実証する
• スカラーを使用する場合と同じくらい単純なSIMD命令にする
我々が達成したいこと
今後の活動
• 混乱をクリーンナップし、boostifyにする
• STL/Boostとの互換性を向上させる
• 募集:このライブラリを実際に活用したアプリケーション
Google Summer of Code 2011
Thanks for your attension