Get container index when using QtConcurrent



  • I was wondering: is it somehow possible to get the index of the item a QtConcurrent::map run is operating on inside the mapping function itself? For instance, for an algorithm I need to fill a large vector with equally spaced values, so basically:
    @
    QVector<double> v(SIZE);
    for (int i(0); i<SIZE; ++i) {
    v[i] = factor * i;
    }
    @

    The above sure works, but I was wondering if I could make use of the multi-core features any modern desktop has, and use QtConcurrent to do the same. All the samples I see operate on the value directly, but in this case the resulting value needs to be dependent on the index of the item, not (only) on the current value.

    I know I could implement a multi-threaded solution manually, but I'm interested to see if I can use the high-level Qt threading primitives for a task like this. Any ideas?



  • Well, that's a good question. I understand that there is no index passed to the map function as not every sequence has the concept of an index but it would be cleary of use in such cases.

    You could use the item itself and some pointer arithmetic to calculate its position within the vector.
    @
    void initialize(double &value, double factor, double *base)
    {
    value = factor * (&value - base);
    }

    QVector<double> vector(SIZE);
    double factor = 10;

    QtConcurrent::map(vector, std::bind(initialize, std::placeholders::_1, factor, vector.data()));
    @
    You will most probably sacrifice an unreasonable amount of performance using <code>bind</code> with such a short codepath of just a multiplication. So a version using globals (for <code>factor</code> and <code>base</code>) might perform better - although beeing somewhat ugly.
    @
    namespace Initializer
    {
    double factor;
    double *base;

    void initialize(double &value)
    {
        value = factor * (&value - base);
    }
    

    };

    QVector<double> vector(SIZE);
    Initializer::factor = 10;
    Initializer::base = vector.data();

    QtConcurrent::map(vector, Initializer::initialize);
    @



  • Interesting approach! Thanks Lukas!

    I think it is a great idea to use the address of the value, as it is passed by (const) reference. That should work, and it would be easy to extend my functor to use that trick too.

    I looked in the source code of QtConcurrent, and I think it would be doable to create a version where the mapping function takes an iterator to the item, instead of the item itself. That would allow for functions like the above (trivial) one, but also for things like smoothing filters that depend not only on the value of the current position, but on the values surrounding it too.



  • You're welcome.

    I'm regularly stretched to QtConcurrents limits when using it (not beeing able to clear a QThreadPools run queue or terminating QRunnables is another nasty, nasty limitation).

    So I'm really looking forward to QtConcurrent contributions ;-)



  • During my lunch walk I thought of a possible limitation to this approach: is it guaranteed that an iterator always returns a reference to the actual value in the container? Might an iterator not return a copy instead, for whatever reason? If a copy is returned, the approach with the pointer artithmatic would be quite dangerous, right?



  • Well, if a copy is passed to the map function you are de-facto screwed.

    However, I see no reason why there should be a copy passed (in both, the immutable -ed() and the mutable version) as long as the compiler is not forced to create a temporary, for example during an implicit conversion if the sequence item type and the map function item type do not match.

    So, yes, the question is if this behaviour is actually guaranteed (that an iterator must not return a copy during iteration). And honestly, I don't know.



  • I've just realized that I totally forgot about another obvious solution, which turned out to be way faster than using <code>bind</code> or a functor and even slightly faster than using globals.
    @
    QVector<double> vector(SIZE);
    double factor = 10;
    double *base = vector.data();

    QtConcurrent::map(vector, [&factor, &base](double &value)
    {
    value = factor * (&value - base);
    });
    @
    Isn't it beautiful? Go C++11 folks!



  • Yes, C++11 is cool for this, but unfortunately my compiler doesn't support these expressions yet. I'd still prefer a version with iterators though, I think:

    @
    QVector<double> vector(SIZE);
    double factor = 10;
    QVector<double>::iterator begin = vector.begin();

    QtConcurrent::map(vector, [&factor, begin](QVector<double>::iterator it)
    {
    value = factor * (it - begin);
    });
    @


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.