Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Efficiency of std::vector copy?



  • I am working on a side project involving audio. I want to pass data around from different nodes. Some nodes may be in different threads depending upon the needs of the system. So I have settled on passing data around using signals and slots. I plan on doing things by value similar to the following:

    void sendSignal(std::vector<double> data);
    or
    struct PassData {
      std::vector<double> data;
      // other data such as sample rate, etc
    };
    void sendSignal(PassData data);
    

    Is it relatively efficient to pass data around with a vector? Is there any caveats I should be aware of? I thought about doing it by reference for speed, but then I have to maintain the original when it could be processing the next sample.

    Edit:
    I suppose QVector might be an option here due to COW as well. Not sure how it would do across threading boundaries though.


  • Moderators

    Passing an std::vector<double> around by value is basically gonna be a bunch of memcopy calls. Not a good idea if you mind performance. As for thread safety the connection itself is guarded, so both emiter and slot threads are fine, but you'd need to guarantee that nothing else is gonna modify the vector when it is being copied, so probably a mutex around the emit call and any other writing thread.

    QVector would be a lot cheaper on emit because of the implicit sharing. The connection is also safer as it only does atomic refcounting, but keep in mind that implicit sharing only guarantees container is reentrant, not thread safe, meaning you would still need a mutex around any call that causes it to detach.

    Using QVector in this scenario is pretty much like using std::shared_ptr<std::vector<double>>. Sharing is protected but access to the data is not and is up to you.



  • As said above, it's a memcpy(), so I would use references when possible, even using a global buffer structure that is shared among threads, and locking it when appropriate with mutexes.



  • To avoid unnecessary copies you can use move semantics to pass vector around, e.g.

    void sendSignal(std::vector<double>&& data);
    // and use it like e.g.
    std::vector<double> data { 1.0, 2.0, 3.0 };
    sendSignal(std::move(data));
    

  • Moderators

    @Kent-Dorfman It's worth mentioning that using const references in cross-thread connections makes a copy anyway, so there's no gain at all in this case.

    @Konstantin-Tokarev That's risky if you have couple of slots (e.g. from different threads) connected. Only the one that happens to trigger first gets valid object.



  • @Chris-Kawa said in Efficiency of std::vector copy?:

    sing const references in cross-thread connections makes a copy anyway

    Interesting. This will affect my decision for references. I will have to play with this a bit. It really depends upon what my life cycle ends up being for each module involved. Do I pass a football around or do I copy and keep them more independent? That will be something I need to figure out.



  • You should actually test which version is fastest. There are too many factors to give a definite answer. Benchmarking and profiling is always the best answer to these kind of questions. Don't optimize something that is irrelevant for the overall performance of your program. And test if there is a difference between std::vector with and without reference and QVector. Then, you will know.


  • Moderators

    @fcarney said in Efficiency of std::vector copy?:

    Interesting. This will affect my decision for references. I will have to play with this a bit. It really depends upon what my life cycle ends up being for each module involved. Do I pass a football around or do I copy and keep them more independent? That will be something I need to figure out.

    If we are talking performance, the best would always be having a common buffer that's not locked at all (i.e. every thread working on its piece). Especially when you manage to align the pieces on the cache line size to have better cache coherency. It'd really depend on what exactly you're doing though, as for most cases the signal-slot mechanism is just fine.

    @Chris-Kawa said in Efficiency of std::vector copy?:

    Using QVector in this scenario is pretty much like using std::shared_ptr<std::vector<double>>. Sharing is protected but access to the data is not and is up to you.

    Although to QVector's advantage in that case it's internally ref-counted, thus you get one indirection less accessing the underlying object (independent of syncing that actual data that is).


Log in to reply