【C++】高速化手法【SSE】2 [転載禁止]©2ch.net
■ このスレッドは過去ログ倉庫に格納されています
C++やインラインアセンブラ、SSEなどによる高速化の手法
について語りましょう。
前スレ
【C++】高速化手法【SSE】
http://peace.2ch.net/test/read.cgi/tech/1130349336/ >>1乙。
10年の時を経て2スレ目に突入、おめでとう。 1スレ10年も掛かったのかwww
compiler intrinsicsの普及と、x64ではインラインアセンブラが使えなくなったのが
またSIMDの再普及に繋がる気がする
SSE3、SSE4、AVX、AVX2、FMAと言った新しい命令も増えているのも理由の一つか マルチコアによるマルチスレッド化の方がずっと楽だから、難しいSIMDプログラミングはいまいち
はやらないのかな SIMDは作るのは難しいが、一度作ってしまうと、関数の中に封じ込めることが出来るので、
後からの使い勝手自体はマルチスレッドよりも良いんだがな。ライブラリ化もただの関数なので問題ない。
マルチスレッドだと関数の中だけで閉じなくて、アプリ全体の設計の部分まで話が広がるので、
機能単位でライブラリ化するのが難しく、簡単に使いまわせない。
ま、SIMDはライブラリ化しやすいから、多くのものが既にライブラリ化されてて、
ほとんどの人はそれを使うだけですむので、自分で書く必要性が少なくて、
故に過疎りやすいってのもあると思うがね。 SIMD化はコンパイラに任せるのが得策。
アルゴリズム改善がどこもできなくなってからでいい。 前スレの8bit単位の乗算とか、そういう頻繁にありそうなケースに対応した命令を用意してくれないと、
間口広がんないよ・・・。 8/16bitぐらいのIndex範囲でテーブル引きしれくれる命令がほしいわ
8bit演算するにも無駄なシャッフルしなくても良い命令がそろったのがSSE4辺りで
さらにAMDが未対応みたいな期間長すぎたな 命令に対応してたってそれを実行するユニットがそれ相応に強化されてなければ大して効果ないよ 並列計算が実装されてないか、使っても遅いとき、通常の演算で並列できないか?
(a0 + a1X + a2X^2 + a3X^3) ( s + tX) のXの1乗、3乗を取り出すことで、 途中送信した。
上のXの項は、t a0 + s a1
上のX^3の項は、t a2 + s a3
Xをたとえば2^10や2^16と取り、下位からの桁上りがないとすると、ビット演算でデータ取り出せる。 >>13>>14を実装して並列計算の叩き台を作った。微妙に速度アップした。結果が正しくないが計算量は正しいはず。
#include <iostream>
#include <time.h>
using namespace std;
#define N (1<<18)
void initdata(unsigned char *a, int range){ for(int n=0; n<2*range; n+=2) { a[n]=rand()%256; a[n+1]=rand()%256; }}
#define cal(x,y,s) {x=(x*s + y*(255-s))&255; y=(y*s + x*(255-s))&255;}
void test(unsigned char *a) {
for(int n=0; n<2*N; n+=2) {
unsigned char x=a[n], y=a[n+1];
for(int s=1; s<255; s++) cal(x,y,s); for(int s=1; s<255; s++) cal(x,y,s); for(int s=1; s<255; s++) cal(x,y,s); for(int s=1; s<255; s++) cal(x,y,s);
a[n]=x; a[n+1]=y; }}
// (a0 + a1X + a2X^2 + a3X^3) ( 255-s + sX) Xの項は、s*a0 + (255-s)*a1 X^3の項は、s*a2 + (255-s)*a3
#define dset(x,y) (x) + ((y)<<8) + ((y)<<16) + ((x)<<24);
#define cal2(x,s) { x *= ( s + ((255-s)<<8) ); unsigned char c=(x>>8)&255, d=(x>>24)&255; x = dset(c,d); }
void test2(unsigned char *a) {
for(int n=0; n<2*N; n+=2) {
unsigned int x = dset(a[n], a[n+1]);
for(int s=1; s<255; s++) cal2(x,s); for(int s=1; s<255; s++) cal2(x,s); for(int s=1; s<255; s++) cal2(x,s); for(int s=1; s<255; s++) cal2(x,s);
a[n]=(x>>8)&255; a[n+1]=(x>>24)&255; }}
int main() {
unsigned char *a = new unsigned char [2*N],*b = new unsigned char [2*N];
initdata(a, N); memcpy(b,a,2*N);
cout<<"memcmp(a,b):"<<(memcmp(a,b,2*N)?"false":"true")<<endl;
unsigned long t=clock(); test(a); t=clock()-t; cout<<t<<endl;
t=clock(); test2(b); t=clock()-t; cout<<t<<endl;
cout<<"memcmp(a,b):"<<(memcmp(a,b,2*N)?"false":"true")<<endl;
return 0; } 前スレ終わりごろのビットマップ合成の話だが、結果的にはスカラ版と比較して
5倍程度に収まっていたが、初期はスカラ版より遅い結果しか出せていなかった。
スカラ版と等速なら話はわかるが、
なぜ遅くなるのか?
俺も似たようなこと遭遇した記憶はあるが
詳細が思い出せない。 SIMD関連はまず命令の実行に都合のいいようにデータを作成するのと
結果を取り出すのに時間がかかるからじゃね?
それとSSEレジスタは128bitなので1処理当たり32bit使うと4並列にしかならない
AVX/AVX2なら256bitなのでこちらの方が速度が上がると思う >>16
構造体へのコピーをスカラで何倍もの時間かけてやってたから
本体処理が4秒が1秒に縮まったところでセットアップに5秒かかってたら意味がない
ボトルネックを解消するつもりでSIMDを使ったつもりが新たな別のボトルネックを作ってました
しかもなんで遅くなったのか使った本人が認識してないというの。 つまりSIMDレジスタで計算する為に
スカラ以上に命令を使った為
遅くなったということか。
前スレの968では見直ししたが2倍ほど遅いと言っていた。
本来4倍になるはずが1/2倍、
8倍も遅くなるなんてありえるか?
根本的に何か問題があるとしか
思えん。 あんなのソースコード見ればボトルネックがどこにあるか一発でわかるだろ
見ただけでわからんやつは向いてないよ 「ハッカーのたのしみ」とかの良著をよく読むことをすすめるよ
並列化アルゴリズムに理解のないやつがいきなりSSEやAVX使っても不幸にしかならない 然しものSIMDも、まったくオーバーヘッドがないわけじゃぁない。
SIMDに演算させるためのデータ構造整えなんかの命令分がオーバーヘッドになる。
これを最小に抑えつつ、SIMDの並列演算性能を活かすのが腕の見せ所よ。 メモリクソ余ってんだから最初からSIMD前提なデータ構造強制できりゃいいのになぁ すいません。以下のようなことがやりたいのですがどうするのが一番いいですか。
void up_index(short a[16],short b[16])
{ すいません。途中で書き込みました。
avx2でお願いします。
void up_index(short a[16],short b[16])
{
for(int i=0;i<15;i++)
{
b[i]=a[i+1];
}
b[15]=0;
}
void down_index(short a[16],short b[16])
{
b[0]=0;
for(int i=1;i<16;i++)
{
b[i]=a[i-1];
}
} void up_index(short a[16],short b[16])
{
__m256i mask = _mm256_setr_epi16(
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0x0000
);
__m256i x = _mm256_loadu_si256((__m256i*)a[1])
x = _mm256_and_si256(x, mask);
_mm256_storeu_si256((__m256i*)b, x);
}
あとわかるよな? ミスった
__m256i x = _mm256_loadu_si256((__m256i*)&a[1]); >>__m256i x = _mm256_loadu_si256((__m256i*)a[1])
領域外(a[16])を参照してるような…
ゴミが入るだけだから気にしなくていいってことですか? うごいてるっぽいので気にしないことにします。
ありがとうございました。 >>ページ境界跨がなきゃ平気
跨ぐとどうなるんでしょうか。
突然プログラムがあぼーんすることも覚悟しなきゃいけないってことですか? おっと間違えた
__attribute__((aligned(32))) 別にアライメントの話じゃないし。
つうか領域外読み込んでも
落ちないからOKとか
それはないから 上下128ビット跨いだシフトが簡単にできないから
ミスアラインロード使わないとめんどくさいんだよねー
void up_index(short a[16],short b[16])
{
__m256i x = _mm256_load_si256((__m256i*)a);
__m128i u = mm_mm256_extracti128_si256(x, 1);
x = _mm256_alignr_epi8( x, _mm256_castsi128_si256(u), 14);
_mm256_store_si256((__m256i*)b, x);
} __m128i u = _mm256_extractf128_si256(x, 1);
みすった 16 x 16 だと結構オーバヘッド覚悟しなきゃいけないみたいですね。
11 x 11 なら128ビットに納まるから上下左右1ビットシフト高速にできますかね? ビットボードでも作ってるの?
11ビットとかかえって扱いづらいからやめとき?pslldq/psrldqは8ビット単位だからいろいろ面倒だぞ
16x16でもvextractf128+vpalignrの2命令ですむんんだからそれが一番無難
2のべき乗のほうが扱いやすい 11 x 11 だと右端と左端がつながっちゃうか。 >>39
はい、まさにビットボードです。
9 x 9 あれば囲碁9路盤が作れます。
やっぱ2のべき乗ですかね〜。 SIMDとか並列プログラムは
各要素間の関連が薄けりゃ薄いほど良い
相関ゼロなら100%の性能が出せるが
逆なら劣化する。
要はお前のプランはSIMD向きじゃねえんだよ。
スカラで組めカス。 なんでよ、ビットボードにSIMD使おうと考えるのは自然だろ。 上下128ビットの入れ替えが面倒なら、1レジスタで1ボードじゃなくて2レジスタで2ボードみたいな割り当てもアリかな 1レジスタに1ボードのらないで2レジスタに2ボードのる状況が想像できない。 me too
碁盤の19x19なら1列32ビットに割り当てて128ビットの4x5レジスタじゃいかんのか?
256ビットなら3レジスタか。ちと苦しいな さて、そろそろ団子が実験結果を公開してくれる頃だと思うんだが……
ソース書けた? 結局SSEとかの機械語でやる以外にC++には高速化の手段がないの?(´・ω・`)
後、変なclassを使わないとか?(´・ω・`) 今はSIMDがあるから
かろうじてC++使ってるけど
他で行けるようになったら
捨てると思う クラスを使うから遅くなるんじゃなくて、動的なポリモーフィズムを実現するための仮想関数テーブルや
あるいは過剰にコンストラクタ呼び出しで遅くなる。 >>53
C++と同程度の速度がでる言語って何よ? いやーそんなん無いけどさー
己が納得出来れば良い訳よ
vector4クラスとかあって
最適化コンパイルでSIMD使いますよ〜
で良い。所詮趣味だし俺はそれで乗り換え出来る。
今はfortranやC++以外にはそれすらも無いからな 過度なオブジェクト指向(笑)やめればC++でもアセンブラに迫る高効率のコード書けますよ 最近はCとアセンブラを比較することも少なくなったろうな。
数学ライブラリなんかはまだアセンブラで書いてんのかねぇ。 >>57
おっさんおっさん、それってCとして使うってのとほぼ同じ意味なんじゃ……? 演算子オーバーロードやテンプレートがCにあるならそうだね
どの程度の抽象化までならオーバーヘッドが生じないかはdvec.hあたりのIntel謹製クラスライブラリ見ればわかるでしょ スレ立てるまででもない質問じゃない質問ってどんなの? え?そこらへん使ってもアセンブラに迫る高効率のコード吐き出してくれるの? >>62
C にはない C++ のオーバーヘッドは、例外と仮想関数呼び出しのための関数テーブルアクセスと dynamic_cast<>()。
それらを使わなければ、型チェックの厳しい C として使える。
演算子オーバーロードは変な名前の関数を呼び出してるだけ。
コンストラクタとか(非仮想)デストラクタは C で同じ作法で作ったのと一緒。
最適化で言うと、俺の環境だと this を restrict として扱わないので、
this->func() を func( this ) にするとか人力で最適化されやすくしないといけないことがある。 >this->func() を func( this ) にするとか
まじで? あとはMSVCだとthisポインタをECXで渡すけどSSE*ってECXを暗黙にdestinationとして扱う命令あるじゃん みんgwでsse2つかってみたけど全然早くならないorz
何か間違ってんのかな? github晒しておくと構いたがりがどこがおかしいのか指摘してくれるよ githubってよく知らないんだよなぁ。
イケてるギークはつかってるらしいが。
これを機にやってみるか… http://www.age2.tv/rd05/src/up9736.zip.html
githubはとりあえず置いといて、ソースうpします。
物は囲連星というゲームの9路盤のAIです。
makefileが入ってるのでbenchmark.exeをmakeしてみてください。
うちのPC(core i7 4790)で40秒くらいで終わるベンチマークができます。
ベンチの内容はAIが3手プレイします。
SSE2で最適化したいクラスはPointSetというクラスで、
128bitつかって 10 x 10 のビットボードを実装しています。
SSE2化してみましたが速くなりませんでした。
スレの皆さん最適化よろしくお願いします。 SSEって最適化できてもせいぜい20%アップ程度の気がするな。
アルゴリズムの見直しとか、CPU演算以外とかはやりきってるか? ちなみにmakefileは依存関係全然考慮してないので毎回クリーンしてくださいw
コンパイラは64bit MinGWです。 オッとリロードしてなかった。
>>71
アルゴリズム見直しで速くなるのが一番いいんですけどね〜
なかなかアイディアが湧かないのでとりあえず手っ取り早いSSEを試してみたいです。 >>73
i7 4790ならAVX2やiGPGPUで速くしたほうがいいんじゃないのか?
データ並列に適していないのにSIMDにしてもたいして速くならないと思うけど
いまの適している? 128bitで一つのビットボードになっていてもろandとかorとかandnotとかさせるので
SSE2で速くなるかなと思ったんですが。
AVX2は256bitですよね?どうやって適応させるか難しいです。
iGPGPUは使い方しらないorz iGPGPUとか全然ダメ。
並列処理向きな処理でも激遅。
最低でもメモリ空間共有がサポートされなきゃゴミ。 >>70はピンポイントでSSE化したい所、出来そうな所だけピックアップしてくれよ。
これでは解析が手間。 ボトルネックがピンポイントで分かってないからこそ遅いのでは? >>77
SSE化できそうなところはPointSetというクラスですが、
じつはもうSSE化してます。
でも速くなんなかった。
なぜ速くならなかったか教えていただけると助かります。
>>78
PointSetボトルネックになってないんですかね。
結構使ってるはずなんですが。 関数ごとのプロファイルとってみて総実行時間を見てみるといいよ
並列化できない(と思ってる)ところが最大のボトルネックになってるなら他をいじってもどうしようもない 最適化ってのはボトルネックになってるところ測定してからやるもんだぞ なんか性能測りたい関数がインライン展開されちゃって測れないっぽいT△T ビットフィールドの立ってるビット数える関数をSSE4.2のpopcnt使ったらえらい速くなったんだが? ちょっと伺いたいんですがavxまでで8bit(1byte)の中で上半分、下半分を取り出すのはありますか?
avx2までいくとpextというmaskをとった部分のビットだけ抽出するのはあるようなんですが・・・ pextというとBMI2かね
汎用レジスタでの話ならシフトとandじゃダメな理由があるのかね
連続したnビットを取り出したいだけならレイテンシの長い(3clk)pextを使う価値があまりない
mov eax, 0fh
mov edx, f0h
pext eax, esi, eax
pext edx, esi, edx
5クロック
mov eax, esi
mov edx, esi
shr edx, 4
and eax, 0fh
and edx, 0fh
3クロック、movが消えるなら2クロック >>90
ちょうどこんな感じです!
このソースみたいにlowerとupperを取り出して返す関数つくろうと思ってました
下4bitはそのまま0xfでmaskして、上4bitは4bitシフトしてからmaskなのでこのコードで行けそうです
ありがとうございます え?
movzx eax, al
shr eax, 4
じゃいかんのか? どうしてもSIMD使いたいんだとさ
クロック数がかえって増えようとも いえアセンブリがわからないだけです!
>>92でいけるのならやってみます!ありがとうございます! gprofの結果ビットボードを座標のベクタに変換する関数PointSet::toVector()が時間かかってることが分かった。
すまん、ちょっと飯食ってくる。 大概は思わぬところでサイクルを食ってるもんだ。
食うのは飯だけにしたいものだな。 PointSet::toVector()は一回最適化試みた箇所なんだよなぁ
正直これ以上アイディアうかばん。 なにをしてるか知らないがここが時間食ってる。
8ビットや16ビットで区切って計算結果を配列に入れといて利用するとかできないか?
int size()const
{
int res=0;
unsigned long long i;
i = u.table[0];
while (i)
{
++res;
i = (i - 1)&i;
}
i = u.table[1];
while (i)
{
++res;
i = (i - 1)&i;
}
return res;
} あとここも。
32bitでの実行だが。
int get(Point p)const
{
// return !((*this) & x_line[p.x()] & y_line[p.y()]).empty();
return (table[p.y() & 1] >> (p.x() + (p.y() >> 1) * 10)) & 1;
} 立ってるビット数が多いときはその数え方だと時間喰いそう ■ このスレッドは過去ログ倉庫に格納されています