###C++のstd::vectorクラスにおいての足し算実装
初歩的なものですが、C++を用いてベクトル同士の足し算を実装中です。
今現在、以下のような単純なもの浮かばないのですが
より高速なアルゴリズムをご存知の方はいませんか?
cpp
1// ベクトルA,Bのサイズは同じ(サイズをNとする) 2vector<int> Addition(vector<int> A, vector<int> B, int N){ 3 vector<int> temp(N); 4 for(int i = 0 ; i < N ; i++){ 5 temp[i] = A[i] + B[i]; 6 } 7 return temp 8} 9 10/* 11例: 12A = [1,2,3,4], B = [5,6,7,8] 13print Addition(A,B) 14> [6,8,10,12] 15*/
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
高速化のための tips は皆さんの助言どおり。
ですがより速い"アルゴリズム"はなさそうです。
ハードウェアの助けを借りて力技でゴリ押すならマルチスレッド(SIMDもアリ)、
もっと欲張るならOpenCLとかCUDAとか。
[追記] ...ってことで、マルチスレッドとCUDAでやってみたわけよ。
C++
1#include <thread> 2#include <vector> 3#include <iostream> 4#include <chrono> 5#include <cassert> 6#include <thread> 7#include <cuda_runtime.h> 8#include <device_launch_parameters.h> 9 10template<typename Function> 11float time_call(Function&& f) { 12 using namespace std::chrono; 13 auto start = high_resolution_clock::now(); 14 f(); 15 auto stop = high_resolution_clock::now(); 16 return duration_cast<microseconds>(stop - start).count() / 1000.0f; 17} 18 19void Addition_single(std::vector<int>& C, const std::vector<int>& A, const std::vector<int>& B, int N) { 20 for ( int i = 0; i < N; ++i ) { 21 C[i] = A[i] + B[i]; 22 } 23} 24 25void Addition_multi(std::vector<int>& C, const std::vector<int>& A, const std::vector<int>& B, int N) { 26 const int nthr = 4; 27 std::thread threads[nthr]; 28 for ( int t = 0; t < nthr; ++t ) { 29 threads[t] = std::thread( 30 [&](int begin, int end, int stride) { 31 for ( int i = begin; i < end; i += stride ) { 32 C[i] = A[i] + B[i]; 33 } 34 }, 35 t, N, nthr); 36 } 37 for ( auto& thr : threads ) thr.join(); 38} 39 40__global__ void kernel(int* pC, const int* pA, const int* pB, int size) { 41 int i = blockDim.x * blockIdx.x + threadIdx.x; 42 if ( i < size ) { 43 pC[i] = pA[i] + pB[i]; 44 } 45} 46 47void Addition_cuda(std::vector<int>& C, const std::vector<int>& A, const std::vector<int>& B, int N) { 48 int* pA; 49 int* pB; 50 int* pC; 51 cudaMalloc(&pA, N*sizeof(int)); 52 cudaMalloc(&pB, N*sizeof(int)); 53 cudaMalloc(&pC, N*sizeof(int)); 54 cudaMemcpy(pA, A.data(), N*sizeof(int), cudaMemcpyHostToDevice); 55 cudaMemcpy(pB, B.data(), N*sizeof(int), cudaMemcpyHostToDevice); 56 kernel<<<(N+255)/256,256>>>(pC, pA, pB, N); 57 cudaMemcpy(C.data(), pC, N*sizeof(int), cudaMemcpyDeviceToHost); 58} 59 60int main() { 61 int N = 2000000; 62 std::vector<int> A(N); 63 std::vector<int> B(N); 64 for ( int i = 0; i < N; ++i ) { 65 A[i] = i; B[i] = -i; 66 } 67 std::vector<int> C(N); 68 69 float single = time_call([&]() { Addition_single(C, A, B, N); }); 70 for ( int i = 0; i < N; ++i ) assert( T[i] == 0 ); 71 std::cout << "single = " << single << "[ms]\n"; 72 73 float multi = time_call([&]() { Addition_multi(C, A, B, N); }); 74 for ( int i = 0; i < N; ++i ) assert( T[i] == 0 ); 75 std::cout << "multi = " << multi << "[ms]\n" ; 76 77 float cuda = time_call([&]() { Addition_cuda(C, A, B, N); }); 78 for ( int i = 0; i < N; ++i ) assert( T[i] == 0 ); 79 std::cout << "cuda = " << cuda << "[ms]\n" ; 80}
結果: ぜーんぜん速くならんです。
thread/CUDAのオーバヘッドが速くなった分を食ってしまう。
時間計算量 O(N) なのを頑張って速くしようと考えん方がむしろ幸せなんじゃないでしょか。
投稿2017/02/09 13:33
編集2017/02/10 00:37総合スコア16614
0
ベストアンサー
vectorを使ってるので要素数を渡す必要は無いですね。size()で得ることが出来ます。
それと、引数は参照にすべきです。そうしないとvectorのコピーが発生するのでオーバーヘッドがかかります。
また、std::transformを使えばもう少し簡単に(forが不要)かけます。
高速かどうかは計ってないのでわかりません。
C++
1#include <algorithm> 2 3using namespace std; 4 5vector<int> Addition(vector<int>& v1, vector<int>& v2) 6{ 7 vector<int> v3 = vector<int>(v1.size()); 8 transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), plus<int>()); 9 return v3; 10}
上記は2つのvectorのサイズがv1のほうがサイズが大きい場合は例外が出ると思います。サイズが違ってたら空っぽのvectorを返すとか、小さいサイズを変換の元にするなどが必要かと思います。その辺は自分なりに工夫してください。
(boost.rangeを使えばtransformはもう少し簡潔に記述できます。)
戻り値を新たに返すのではなく、例えば引数のv1に返すという手もあります。そうすれば戻り値を返す時のコピーを防げます。
C++
1#include <algorithm> 2 3using namespace std; 4 5void Addition(vector<int>& v1, const vector<int>& v2) 6{ 7 transform(v1.begin(), v1.end(), v2.begin(), v1.begin(), plus<int>()); 8}
ただ、これなら1行で書けるので関数にする必要もなくなるという考えもあります。
更に追記です。
vectorはデバッグビルドだとかなり速度が遅くなります(VC++ではそうです)。なので、リリースビルドすれば高速に動作する可能性があります。
投稿2017/02/09 08:22
編集2017/02/09 23:02総合スコア3579
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
高速化の要点をまとめます。
- vectorオブジェクトは参照渡しで
PineMatsuさんが指摘するとおり、値渡しだと各要素のコピーが発生し、パフォーマンス低下の要因となります。もしvectorのサイズが100万だった場合100万件分のコピーが発生します。
- vectorオブジェクトはreturnで返さない
Chironianさんが書かれているように、場合によってはRVOの仕組みによりコピーが省かれることもありますが、常にRVOが働くとは限らないので、参照渡しの引数で結果を返した方がパフォーマンス的には有利です。
- 「X = A + B」ではなく、「A += B」とする
オブジェクトの作成と破棄には相応のコストがかかります。前者ではオブジェクトを1個余分に作る必要があるため、パフォーマンスに影響が出ます。場合によってはメモリへの負荷も気になります。
- vectorの各要素のアクセスは、インデックスではなくイテレーターを使う
組み込み配列のインデックス指定だと、今時のコンパイラーならかなりの最適化が期待できますが、vectorのインデックス指定だと最適化があまり効かず、毎回アドレス計算を行ってしまい、高速化の足かせになってしまいます。
その点、イテレーターは中身はポインタなのでアドレス計算のコストが省けます。
- いっそのこと生ポインタを使う
とにかく最大限に高速化したいという場合は、最終的にはこれになります。
vectorの要素はメモリ内で連続して配置されていることが保証されているので、イテレーターの代わりに生ポインタによる連続アクセスが可能です。生ポインタによるアクセスは最適化が効きやすいので高速化が期待できます。
ただし、バッファオーバーランなどの問題を引き起こしやすいので、慎重にコードを書かないといけません。
投稿2017/02/09 13:26
編集2017/02/09 13:28総合スコア5938
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
こんにちは。
高速化ポイントとしては2つ思いつきますが、その両方とも最適化により自動的に高速化されてしまうだろうと思います。
- for文
最適化されない場合、indexではなくてiteratorを使った方が、微妙に速くなるCPUもあると思います。
- return文
文法通りに解釈するとこの返し方の場合、std::vector<int>のコピーが発生するので遅いのですが、最近のPC用コンパイラでは、RVO(Return Value Optimization)と言う最適化が働いてコピーされなくなると思います。
投稿2017/02/09 08:08
編集2017/02/09 08:10総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/02/09 08:34
2017/02/09 09:04
2017/02/09 09:06
0
vectorの要素が100万くらいで、要素の型がintなら実際にわずかな時間しかかからないし、普通のプログラムであれば、全体に対してもほんの僅かなのでは?オイラの環境では、10^7要素で0.1秒です。究極の方法使って最大0.1秒節約。全体が10秒であれば、1%の節約。
profilerを使ってどこで時間がかかっているか調べましょう。要素の型がもっと複雑であれば、その型に応じた最適化があるかも です。
g++ -O3 使って実行時間が変わるか、openMPをloop部分に使って、
<p>#pragma omp parallel for for (int i = 0; i < size; ++i) </p> g++ -fopenmp で試す。 というあたりからがいいのでは?投稿2017/02/19 14:35
総合スコア580
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。