Concurrency in C++, the cross platform way

My blogging activity has been low recently, so here’s some C++ musings on concurrency/parallelism.

std::async woes

C++11 introduced low level threading facilities into the standard library, but also a potentially handy higher level function, std::async, that can run callables in other threads. Unfortunately the C++ standard left open how that should be implemented. Microsoft uses a task based implementation  for it since Visual Studio 2012 but C++ standard libraries from other vendors unfortunately use a threading based implementation. That means that the following code will likely launch 1000 threads with the non-Microsoft implementations :

void do_lots_of_stuff()
{
  for (int i=0;i<1000;++i)
    std::async(std::launch::any,[](){ //...suitably heavy processing here... });
  // Since we don't wait here for the threads to finish, this'll crash...
  // But this is example code, so just imagine here's something that waits
}

That is definitely something we don’t want, 1000 threads would have a considerable overhead. The Microsoft implementation on the other hand will attempt to use a more balanced number of threads, passing the work to be done via tasks into its Concurrency Runtime. (The Microsoft implementation doesn’t necessarily throttle the number of threads at maximum to be the number of CPU cores in the system, but it will likely be way less than 1000 threads with the code example above.)

Apple of course has its Grand Central Dispatch, aka libdispatch, that is designed to allow running code concurrently using a higher level API and which uses an optimized number of threads. Unfortunately, their C++ library does not take advantage of that for std::async. I can only speculate why : they simply don’t like developers using C++ and try to make it as unattractive as possible to use the C++ language on their precious elitist platforms. Making std::async not use Grand Central Dispatch could be considered one subtle way how they are nudging developers towards using Objective-C and Cocoa, which are not cross platform in any real world sense.

Fortunately, Sean Parent presented a replacement for std::async that uses Grand Central Dispatch on OS-X. The code is a bit hairy, but compiles and runs as expected. (The code seen in his slides in the “C++ seasoning” talk has an error, but the corrected version I got from Sean is given here.)

#include "dispatch/dispatch.h"
template <typename F, typename ...Args>
auto async(F&& f, Args&&... args)
-> std::future<typename std::result_of<F (Args...)>::type>
{
    using result_type = typename std::result_of<F (Args...)>::type;
    using packaged_type = std::packaged_task<result_type ()>;

    auto p = new packaged_type(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    auto result = p->get_future();
    dispatch_async_f(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                     p, [](void* f_) {
        packaged_type* f = static_cast<packaged_type*>(f_);
        (*f)();
        delete f;
    });

    return result;
}

For Windows, we can have a simple wrapper for std::async, so we can write code that calls async the same way on both Windows and OS-X and get the desired behavior where the number of threads doesn’t explode :

template <typename F, typename ...Args>
auto async(F&& f, Args&&... args)
-> std::future<typename std::result_of<F (Args...)>::type>
{
    return std::async(std::launch::any,std::forward<F>(f),std::forward<Args>(args)...);
}

std::parallel_for_e…Oh crap, it doesn’t exist!

The C++ standards committee didn’t add even the simplest parallel algorithms for the C++11 standard library and they are not going to be seen in C++14, either. If we could have at least a parallel for loop that goes over a container and could do something with the elements, things would be a bit more happy…

Microsoft offers this in its Parallel Patterns Library, PPL. Use is rather simple since PPL is a proper C++ library, so it can use templates, iterators and lambdas :

#include "ppl.h"
void foo()
{
  std::vector<object_t> data(1000);
  Concurrency::parallel_for_each(data.begin(), data.end(), [](object_t& e) { // manipulate e });
}

Now, of course it’s not so great having to use a Microsoft specific library in our client code…And what about OS-X? Again, Apple’s Grand Central Dispatch also has a function that is similar to this, dispatch_apply_f. However, that is not only an Apple-specific API but also C, which is a language we don’t want to deal with directly in our client code.

So, I put on my thinking cap and was able to come up with a C++ function template, that uses the GCD/libdispatch API…(Someone probably has already done a better version of this but I wanted to see if I could manage to do it myself.)

#include "dispatch/dispatch.h"
template<typename It, typename F>
inline void parallel_for_each(It a, It b, F&& f)
{
    size_t count=std::distance(a,b);
    using data_t=std::pair<It,F>;
    data_t helper=data_t(a,std::forward<F>(f));
    dispatch_apply_f(count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), &helper, [](void* ctx,size_t cnt)
    {
        data_t* d=static_cast<data_t*>(ctx);
        auto elem_it=std::next(d->first,cnt);
        (*d).second(*(elem_it));
    });
}

Again, some rather nasty looking code there, but anything goes as long as it’s in the bowels of library code!

Like for async(), we can do a simple wrapper function for Windows which calls PPL’s Concurrency::parallel_for_each :

#include "ppl.h"
template<typename It, typename F>
inline void parallel_for_each(It a, It b, F&& f)
{
    Concurrency::parallel_for_each(a,b,std::forward<F>(f));
}

Hopefully someone finds these code snippets interesting/useful. They are not guaranteed to be error-free or as efficient as possible. They rather serve as examples how Microsoft’s PPL and Apple’s Grand Central Dispatch/libdispatch can be wrapped into library C++ functions that can be used in a cross-platform manner in client code. (Mentioning Linux was omitted here but I believe the libdispatch stuff should work on it too, I just didn’t investigate and test that.)

Advertisements
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s