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

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

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

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

Q&A

解決済

3回答

3783閲覧

C++ - std::vector の配列の一部の参照 (View) を作成したい。

tiitoi

総合スコア21954

C++

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

0グッド

2クリップ

投稿2019/03/19 09:51

編集2019/03/19 15:53

環境

  • C++11 (C++14, 17, 20 で実現可能なら他のバージョンでも可)
  • Visual Studio 2017

質問内容

std::vector の配列の各要素を関数に渡して、ある処理を行って要素の値を更新するとします。
現在は以下のように順番に各要素に処理を行っていますが、1つの処理の計算量が重く、独立して行える処理のため、マルチスレッドで行いたいと思いました。

cpp

1for(auto &v: vec) 2 some_process(v);

そのため、std::vector の配列をスレッド数分に分割して関数に渡して処理したいと思ったのですが、C++ で Python のように「配列の一部の参照を作成して、関数に渡す」ということはできるのでしょうか。
現在は指定範囲のイテレータを渡すようにしていますが、よりよい方法があれば、ご教示いただけないでしょうか。

cpp

1#include <algorithm> 2#include <cmath> 3#include <iostream> 4#include <numeric> 5#include <thread> 6#include <vector> 7 8class SomeClass 9{ 10 public: 11 void process( 12 std::vector<int>::iterator &begein, 13 std::vector<int>::iterator &end, 14 int mul) const 15 { 16 // vec の各要素になにかの処理、以下は例であり、実際は重たい処理になります。 17 std::for_each( 18 begein, end, [mul](int &v) { v = v * mul; }); 19 } 20}; 21 22 23int main() 24{ 25 SomeClass obj; 26 27 // 初期化 28 std::vector<int> vec(50); 29 // vec = {1, 2, ..., 50} 30 std::iota(vec.begin(), vec.end(), 1); 31 std::for_each( 32 vec.begin(), vec.end(), [](int &v) { std::cout << v << std::endl; }); 33 34 // マルチスレッドで処理 35 const int num_threads = 5; // スレッド数 36 std::vector<std::thread> threads; 37 38 // スレッドを作成する。 39 for (int i = 0; i < num_threads; ++i) { 40 auto begin = vec.begin() + i * 10; 41 auto end = begin + 10; 42 43 threads.push_back(std::thread( 44 &SomeClass::process, &obj, std::ref(begin), std::ref(end), i)); 45 } 46 47 // スレッドの終了待機 48 for (std::thread &t : threads) 49 t.join(); 50 51 // vec = {1*0, 2*0, ..., 9*0, 10*1, 11*1, ...} 52 std::for_each( 53 vec.begin(), vec.end(), [](int &v) { std::cout << v << std::endl; }); 54}

聞きたいこと

  • std::vector の配列の一部の参照を作る方法について。
  • 配列の各要素を(同じ要素にアクセスすることはないとはいえ)別々のスレッドから変更することに問題はないかどうか。

※ 上記の配列をスレッド数分分割して、スレッドごとに処理するというのは、とりあえず思いついた方法なので、それ以外の方法で高速化可能なら方法はなんでもいいです。

すみませんが、よろしくおねがいします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

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

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

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

guest

回答3

0

ベストアンサー

配列の各要素を(同じ要素にアクセスすることはないとはいえ)別々のスレッドから変更することに問題はないかどうか。

はい。「std::vector<T>の異なる要素に対する別々スレッドからの変更操作」の安全性は、C++標準ライブラリ仕様で保証されます。記事「スレッドセーフという幻想と現実」もご参考にください。

std::vector の配列の一部の参照を作る方法について。

C++17現在は「区間の開始/終端位置を表すイテレータ・ペア」として表現するのが妥当かと思います。

C++20からは、std::vectorのような連続メモリ領域に対して部分区間を表す std::spanクラステンプレート が導入されます。本質的にはイテレータペアと等価ですが、“区間”を表す単一オブジェクトとして扱えるメリットはあります。


質問中のコード実装では、★箇所でダングリング参照による並行動作バグを引き起こします。変数begin, endは「forループの各反復処理中でのみ有効なイテレータオブジェクト」を指しますが、★では“参照”として別スレッドへと渡しています。つまり新しいスレッドが処理を始めるタイミングでは、既に該当イテレータオブジェクトが破棄されている 可能性 があり、意図しない不定動作やメモリ破壊などの不具合を引き起こします。

c++

1for (int i = 0; i < num_threads; ++i) { 2 auto begin = vec.begin() + i * 10; 3 auto end = begin + 10; 4 5 threads.push_back(std::thread(&SomeClass::process, &obj, 6 std::ref(begin), std::ref(end), // ★ 7 i)); 8}

スレッド間をまたいで値(このケースではイテレータオブジェクト)を受け渡す場合、単に値を“コピー”するか、オブジェクトの有効範囲を注意深く制御した上で参照渡しを行なってください。


現在は以下のように順番に各要素に処理を行っていますが、1つの処理の計算量が重く、独立して行える処理のため、マルチスレッドで行いたいと思いました。

C++17標準ライブラリ範囲内であれば、並列アルゴリズムが提供されます。ただし、ご利用中の各コンパイラ/ライブラリが対応済みかは別途ご確認ください。

c++

1#include <algorithm> 2#include <execution> 3 4std::for_each( 5 std::execution::par, // スレッド並列処理を指示 6 std::begin(vec), std::begin(end), [&](auto&& e) { 7 // 1要素に対する処理 8 });

外部OSSライブラリの利用でもよければ、Intel TBBもオススメです。ライセンスもPermissiveです。

投稿2019/03/19 15:48

編集2019/03/19 16:00
yohhoy

総合スコア6189

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

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

tiitoi

2019/03/19 16:05 編集

ご指摘いただいた通り、イテレータが次のループにいったら有効でなくなってしまうことを見落としてました。実際、本日マルチスレッド化してから不定期に core dump する現象が発生していて、デバッグしていたのですが、原因がつかめていませんでした。それが原因の可能性がかなり高そうなので、明日確認してみます。 > 単に値を“コピー”するか イテレータ自体、std の関数にわたすか、for 文でしか使ったことがなかったので理解が不十分なのですが、イテレータを値渡しして、渡した先で値の変更を行った場合、元の配列に反映されるのでしょうか? てっきり参照渡しかポインタ渡ししないと、渡した側に変更が伝わらないと思って参照渡しにしていました。
yohhoy

2019/03/19 16:11

(少々乱暴な説明ですが)vectorコンテナとイテレータの関係は、配列とポインタの関係と同じです。イテレータのコピー/値渡しは、ポインタ値のコピー/値渡しと同等の解釈で問題ありません。 > イテレータを値渡しして、渡した先で値の変更を行った場合、元の配列に反映されるのでしょうか? 以上より、複製されたイテレータ経由の変更であっても期待通り元コンテナに反映されます。
tiitoi

2019/03/19 16:20

なるほど。理解しました。 C++ のイテレータの理解がまだ怪しいので勉強してみます。 また C++20 なので、使える環境がすぐには用意できないですが、ご紹介いただいた std::span が当初やりたかったことに近そうです。
tiitoi

2019/03/19 16:23

イテレータのスコープのバグを発見してくださった yohhoy さんを BA にさせていただきましたが、 t_obara さん、Chironian さん、yohhoy さんの回答は、どれも大変参考になりました。 皆さん、丁寧がご回答ありがとうございます。
t_obara

2019/03/20 02:51

std::spanは知りませんでした、参考になりました。
guest

0

こんにちは。

std::vector の配列の一部の参照を作る方法について。

現在のSTLの範疇ではご提示されている内容は妥当と思います。
C++11で規定された範囲ベースforと、まだ残念ながら規定されていないrangeを使えば多少見やすくなりますが、本質は変わりません。

配列の各要素を(同じ要素にアクセスすることはないとはいえ)別々のスレッドから変更することに問題はないかどうか。

std::vectorに要素を追加・削除しなければ問題ないです。(異なる領域を別スレッドで変更しても問題は発生しません。)

※ 上記の配列をスレッド数分分割して、スレッドごとに処理するというのは、とりあえず思いついた方法なので、それ以外の方法で高速化可能なら方法はなんでもいいです。

スレッド・プールを実装して処理することが考えられます。
スレッドへの要求キューを作り、std::vector内のデータ1つに対して1つエンキューし、用意した複数のスレッドへ次々とそれらの要求を割り当てていくイメージです。
スレッド・プールの実装方法は様々なものが議論されていると聞きますので、もしかすると使いやすいものがあるかも知れません。

これを使えば個々の処理時間がバラついても、CPU使用率を最大化できると思います。
(ご提示のコードの場合、各10個のセットの内、例えばどれか1つだけ極端に時間がかかってしまうと、他のスレッドが先に終了してしまい、CPUの使用率は落ちます。)

投稿2019/03/19 12:51

Chironian

総合スコア23272

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

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

tiitoi

2019/03/19 16:11 編集

> 現在のSTLの範疇ではご提示されている内容は妥当と思います。 Python をよく使うのでリストの slice のようなやり方を探していたのですが、言われてみれば、stl の関数はほとんど begin, end を引数としてとるようになっているので、今のやり方でも C++ ではよいかもしれませんね。 > スレッド・プールを実装して処理することが考えられます。 スレッドプールという設計は知りませんでした。 ぜひ使ってみたいので、既存のライブラリを探してみます。 ご回答ありがとうございました。
tiitoi

2019/03/19 16:15

ご紹介いただいた std::experimental::ranges::Range が当初やりたかったことに近いかもしれません。 STL の導入予定は今の所ないのですね。。
yohhoy

2019/03/19 16:23

FYI: Range自体はC++20標準ライブラリ導入が決まっていますよ。実際に普及するのはもう少し未来になると思いますけど;P https://en.cppreference.com/w/cpp/ranges/Range おそらくstd::range::subrange が求めるモノになるのではと思います。
tiitoi

2019/03/19 16:26

なるほど。C++11 以降に追加されたまたは追加予定の STL の関数を全然把握できていないので、勉強します。ご紹介ありがとうございます。
guest

0

マルチコアなシステムであれば、とりあえずマルチスレッド化する効果が期待できると思いますが、まずは測定してから取り組むことをお勧め致します。
各要素を変更するのみであれば、スレッドセーフです。
https://ja.cppreference.com/w/cpp/container

ただ、渡した後の処理が重いのであれば、assignで渡したとしても微々たる影響ではないでしょうか。

投稿2019/03/19 10:25

t_obara

総合スコア5488

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

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

tiitoi

2019/03/19 15:58

> 各要素を変更するのみであれば、スレッドセーフです。 なるほど。スレッドセーフとのことで安心しました。 記載いただいたリンクの「スレッド安全性」についても読んでみます。 丁寧なご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問