質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

18073閲覧

【C++】【vectorクラス】高速なベクトル足し算

RyuSA

総合スコア131

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

4クリップ

投稿2017/02/09 07:51

###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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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
episteme

総合スコア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
PineMatsu

総合スコア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
catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

高速化ポイントとしては2つ思いつきますが、その両方とも最適化により自動的に高速化されてしまうだろうと思います。

  1. for文

最適化されない場合、indexではなくてiteratorを使った方が、微妙に速くなるCPUもあると思います。

  1. return文

文法通りに解釈するとこの返し方の場合、std::vector<int>のコピーが発生するので遅いのですが、最近のPC用コンパイラでは、RVO(Return Value Optimization)と言う最適化が働いてコピーされなくなると思います。

投稿2017/02/09 08:08

編集2017/02/09 08:10
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2017/02/09 08:27 編集

PineMatsuさんが書かれている引数側の問題を見落としてました。 確かに要素数は態々渡す必要はない筈です。またstd::vector<>の引数は参照かポインタにしないとこちらでコピーが発生します。それは痛いです。
PineMatsu

2017/02/09 08:34

C++11だったら範囲forを使う手もありますが速度的にイテレータとくらべてどうなんでしょうね?処理系に依存するのかな?
Chironian

2017/02/09 09:04

範囲ベースforも中身はイテレータです。ただ、毎回end()関数を呼ぶことになっているので、そこも最適化したい場合はイテレータの方が良いだろうと思います。
PineMatsu

2017/02/09 09:06

なるほど。終了条件をforの外に出してしまうということですね。ありがとうございます。
guest

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

gm300

総合スコア580

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問