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

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

ただいまの
回答率

89.12%

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 3,825

tiitoi

OpenCV総合1位

環境

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

質問内容

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

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

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

#include <algorithm>
#include <cmath>
#include <iostream>
#include <numeric>
#include <thread>
#include <vector>

class SomeClass
{
  public:
    void process(
        std::vector<int>::iterator &begein,
        std::vector<int>::iterator &end,
        int mul) const
    {
        // vec の各要素になにかの処理、以下は例であり、実際は重たい処理になります。
        std::for_each(
            begein, end, [mul](int &v) { v = v * mul; });
    }
};


int main()
{
    SomeClass obj;

    // 初期化
    std::vector<int> vec(50);
    // vec = {1, 2, ..., 50}
    std::iota(vec.begin(), vec.end(), 1);
    std::for_each(
        vec.begin(), vec.end(), [](int &v) { std::cout << v << std::endl; });

    // マルチスレッドで処理
    const int num_threads = 5;  // スレッド数
    std::vector<std::thread> threads;

    // スレッドを作成する。
    for (int i = 0; i < num_threads; ++i) {
        auto begin = vec.begin() + i * 10;
        auto end = begin + 10;

        threads.push_back(std::thread(
            &SomeClass::process, &obj, std::ref(begin), std::ref(end), i));
    }

    // スレッドの終了待機
    for (std::thread &t : threads)
        t.join();

    // vec = {1*0, 2*0, ..., 9*0, 10*1, 11*1, ...}
    std::for_each(
        vec.begin(), vec.end(), [](int &v) { std::cout << v << std::endl; });
}

聞きたいこと

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+5

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

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

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

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

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


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

for (int i = 0; i < num_threads; ++i) {
  auto begin = vec.begin() + i * 10;
  auto end = begin + 10;

  threads.push_back(std::thread(&SomeClass::process, &obj,
    std::ref(begin), std::ref(end),  // ★
    i));
}

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


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

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

#include <algorithm>
#include <execution>

std::for_each(
  std::execution::par,  // スレッド並列処理を指示
  std::begin(vec), std::begin(end), [&](auto&& e) {
    // 1要素に対する処理
  });

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/20 01:20

    なるほど。理解しました。
    C++ のイテレータの理解がまだ怪しいので勉強してみます。

    また C++20 なので、使える環境がすぐには用意できないですが、ご紹介いただいた std::span が当初やりたかったことに近そうです。

    キャンセル

  • 2019/03/20 01:23

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

    キャンセル

  • 2019/03/20 11:51

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

    キャンセル

+2

こんにちは。

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

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/20 01:11 編集

    > 現在のSTLの範疇ではご提示されている内容は妥当と思います。

    Python をよく使うのでリストの slice のようなやり方を探していたのですが、言われてみれば、stl の関数はほとんど begin, end を引数としてとるようになっているので、今のやり方でも C++ ではよいかもしれませんね。

    > スレッド・プールを実装して処理することが考えられます。

    スレッドプールという設計は知りませんでした。
    ぜひ使ってみたいので、既存のライブラリを探してみます。

    ご回答ありがとうございました。

    キャンセル

  • 2019/03/20 01:15

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

    キャンセル

  • 2019/03/20 01:23

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

    キャンセル

  • 2019/03/20 01:26

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

    キャンセル

+1

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/20 00:58

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

    丁寧なご回答ありがとうございました。

    キャンセル

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

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる