検索中に見つけた紹介リンク:
ご覧のとおり、ほとんどがC用ですが、C++でも動作するのではないかと思いました。これが私のコードです:
template<typename T>
//__attribute__((optimize("unroll-loops")))
//__attribute__ ((pure))
void foo(std::vector<T> &p1, size_t start,
size_t end, const std::vector<T> &p2) {
typename std::vector<T>::const_iterator it2 = p2.begin();
//#pragma simd
//#pragma omp parallel for
//#pragma GCC ivdep Unroll Vector
for (size_t i = start; i < end; ++i, ++it2) {
p1[i] = p1[i] - *it2;
p1[i] += 1;
}
}
int main()
{
size_t n;
double x,y;
n = 12800000;
vector<double> v,u;
for(size_t i=0; i<n; ++i) {
x = i;
y = i - 1;
v.Push_back(x);
u.Push_back(y);
}
using namespace std::chrono;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
foo(v,0,n,u);
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took me " << time_span.count() << " seconds.";
std::cout << std::endl;
return 0;
}
上記のコメントを見ることができるすべてのヒントを使用しましたが、サンプル出力が示すように、スピードアップは得られませんでした(最初の実行ではコメントが外されています#pragma GCC ivdep Unroll Vector
:
samaras@samaras-A15:~/Downloads$ g++ test.cpp -O3 -std=c++0x -funroll-loops -ftree-vectorize -o test
samaras@samaras-A15:~/Downloads$ ./test
It took me 0.026575 seconds.
samaras@samaras-A15:~/Downloads$ g++ test.cpp -O3 -std=c++0x -o test
samaras@samaras-A15:~/Downloads$ ./test
It took me 0.0252697 seconds.
希望はありますか?または最適化フラグO3
トリックをするだけですか?このコード(foo
関数)を高速化するための提案は大歓迎です!
私のバージョンのg ++:
samaras@samaras-A15:~/Downloads$ g++ --version
g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1
ループの本体がランダムであることに注意してください。私はそれを他の形で書き直すことに興味がありません。
編集
これ以上できることはないという答えも受け入れられます!
O3
フラグは-ftree-vectorizeを自動的にオンにします。 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
-O3は、-O2で指定されたすべての最適化をオンにし、-finline-functions、-funswitch-loops、-fpredictive-commoning、-fgcse-after-reload、-ftree-loop-vectorize、-ftree-loop-distributeもオンにします。 -patterns、-ftree-slp-vectorize、-fvect-cost-model、-ftree-partial-pre、および-fipa-cp-cloneオプション
したがって、どちらの場合も、コンパイラはループベクトル化を実行しようとしています。
G ++ 4.8.2を使用してコンパイルします。
g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorize -ftree-vectorizer-verbose=1 -o test
これを与える:
Analyzing loop at test.cpp:16
Vectorizing loop at test.cpp:16
test.cpp:16: note: create runtime check for data references *it2$_M_current_106 and *_39
test.cpp:16: note: created 1 versioning for alias checks.
test.cpp:16: note: LOOP VECTORIZED.
Analyzing loop at test_old.cpp:29
test.cpp:22: note: vectorized 1 loops in function.
test.cpp:18: note: Unroll loop 7 times
test.cpp:16: note: Unroll loop 7 times
test.cpp:28: note: Unroll loop 1 times
-ftree-vectorize
フラグなしでコンパイル:
g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorizer-verbose=1 -o test
これだけを返します:
test_old.cpp:16: note: Unroll loop 7 times
test_old.cpp:28: note: Unroll loop 1 times
16行目はループ関数の始まりなので、コンパイラーは間違いなくそれをベクトル化しています。アセンブラをチェックすると、これも確認されます。
現在使用しているラップトップで積極的なキャッシュが発生しているようです。そのため、関数の実行にかかる時間を正確に測定することが非常に困難になっています。
しかし、他にも試すことができることがいくつかあります。
__restrict__
修飾子を使用して、配列間にオーバーラップがないことをコンパイラーに通知します。
配列が__builtin_assume_aligned
(移植性がない)と整列していることをコンパイラーに伝えます
結果のコードは次のとおりです(データ型ごとに異なる配置を使用する必要があるため、テンプレートを削除しました)
#include <iostream>
#include <chrono>
#include <vector>
void foo( double * __restrict__ p1,
double * __restrict__ p2,
size_t start,
size_t end )
{
double* pA1 = static_cast<double*>(__builtin_assume_aligned(p1, 16));
double* pA2 = static_cast<double*>(__builtin_assume_aligned(p2, 16));
for (size_t i = start; i < end; ++i)
{
pA1[i] = pA1[i] - pA2[i];
pA1[i] += 1;
}
}
int main()
{
size_t n;
double x, y;
n = 12800000;
std::vector<double> v,u;
for(size_t i=0; i<n; ++i) {
x = i;
y = i - 1;
v.Push_back(x);
u.Push_back(y);
}
using namespace std::chrono;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
foo(&v[0], &u[0], 0, n );
high_resolution_clock::time_point t2 = high_resolution_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took me " << time_span.count() << " seconds.";
std::cout << std::endl;
return 0;
}
私が言ったように、一貫した時間測定値を取得するのに苦労したので、これがパフォーマンスの向上(またはおそらく低下さえも!)をもたらすかどうかを確認できません
GCCには、SIMD命令を使用する新しいプリミティブを作成するコンパイラの拡張機能があります。詳細は こちら をご覧ください。
ほとんどのコンパイラーは、操作を自動ベクトル化すると言っていますが、これはコンパイラーのパターンマッチングに依存しますが、ご想像のとおり、これは非常に失敗する可能性があります。