C++ multi threading: how do I reuse threads for many "jobs"?

Viewed 636

I'm learning c++ and was making a realtime raytracer. First I used std::thread to spread the work, but turned out starting 32 threads every frame is way slower than the actual work that needs to be done.

Then I found C++ also uses threadpools to deal with that issue through std::async():

void trace()
{
    constexpr unsigned int thread_count = 32;
    std::future<void> tasks[thread_count];

    for (auto i = 0u; i < thread_count; i++)
            tasks[i] = std::async(std::launch::async, raytrace_task(sample_count, world_), 10, 10);

    for (auto i = 0u; i < thread_count; i++)
        tasks[i].wait();
}

and the raytrace_task is empty:

struct raytrace_task
{
    // simple ctor omitted for brevity

    void operator()(int y_offset, int y_count)
    {
        
    }
}

But this is just as slow as making your own threads. Each call to trace() takes about 30ms! Can anyone tell me what I'm doing wrong or how to reuse threads? aka: post many data-processing jobs to a single reused thread throughout time.

thank you

1 Answers

Thanks for all the comments. I ended up incorporating Ted Lyngmo's example which improved performance from 80ms to 7ms per frame using all my cores.

The task struct:

#ifdef __cpp_lib_hardware_interference_size
  using std::hardware_constructive_interference_size;
  using std::hardware_destructive_interference_size;
#else
  constexpr std::size_t hardware_constructive_interference_size = 2 * sizeof(std::max_align_t);
  constexpr std::size_t hardware_destructive_interference_size = 2 * sizeof(std::max_align_t);
#endif

struct alignas(hardware_destructive_interference_size) raytrace_task
{
    // ctor omitted

    void operator()()
    {
        // raytrace one screen-chunk here
    }
}

and the code triggering the raytracing each frame:

#include <execution>

// ...

void trace()
{
    const auto thread_count = std::thread::hardware_concurrency();

    // generate render-chunks into multiple raytrace_tasks:
    std::vector<raytrace_task> tasks;
    for (auto i = 0u; i < thread_count; i++)
    {
        tasks.push_back(raytrace_task(world_, i, thread_count, camera_, screen_));
    }

    // run the raytrace_tasks:
    std::for_each(std::execution::par, tasks.begin(), tasks.end(), [](auto& task) { task(); });
}

Note: I also had to set Visual Studio to compile in C++17 (project properties > C/C++ > Language)

Related