Search code examples
c++openmpvectorizationcondition-variablestdthread

C++ condition variables vs new threads for vectorization


I have a block of code that goes through a loop. A section of the code operates on a vector of data and I would like to vectorize this operation. The idea is to split the elaboration of the array on multiple threads that will work on subsections of the array. I have to decide between two possibilities. The first one is to create the threads each time this section is encountered an rejoin them at the end with the main thread:

for(....)
{
//serial stuff

//crate threads
for(i = 0; i < num_threads; ++i)
{
    threads_vect.push_back(std::thread(f, sub_array[i]));
}

//join them
for(auto& t : threads_vect)
{
    t.join();
}

//serial stuff
}

This is similar at what it is done with OpenMP, but since the problem is simple I'd like to use std::threads instead of OpenMP (unless there are good reasons against this).

The second option is to create the threads beforehand to avoid the overhead of creation and destruction, and use condition variables for synchronization (I omitted a lot of stuff for the synchronization. It is just the general idea):

std::condition_variable cv_threads;
std::condition_variable cv_main;

//create threads, the will be to sleep on cv_threads

for(....)
{
//serial stuff

//wake up threads
cv_threads.notify_all();

//sleep until the last thread finishes, that will notify.
main_thread_lock.lock();
cv_main.wait(main_lock);

//serial stuff
}

To allow for parallelism the threads will have to unlock the thread_lock as soon as they wake up at the beginning of the computation, then acquire it again at to go to sleep and synchronize between them to notify the main thread.

My question is which of this solutions is usually preferred in a context like this, and if the avoided overhead of thread creation and destruction is usually worth the added complexity (or worth at all given that the added synchronization also adds time).

Obviously this also depends on how long the computation is for each thread, but this could vary a lot since the length of the data vector could also be very short (to about two element per thread, that would led to a computation time of about 15 milliseconds).


Solution

  • The biggest disadvantage of creating new threads is that thread creation and shutdown is usually quite expensive. Just think of all the things an operating system has to do to get a thread off the ground, compared to what it takes to notify a condition variable.

    Note that synchronization is always required, also with thread creation. The C++11 std::thread for instances introduces a synchronizes-with relationship with the creating thread upon construction. So you can safely assume that thread creation will always be significantly more expensive than condition variable signalling, regardless of your implementation.

    A framework like OpenMP will typically attempt to amortize these costs in some way. For instance, an OpenMP implementation is not required to shut down the worker threads after every loop and many implementations will not do this.