liveBook · Manning

Hardware concurrency

As explained in C++ Concurrency in Action, Second Edition

Hardware concurrency is the degree of parallel execution supported by the available hardware.

Hardware concurrency is the number of independent tasks a computer’s hardware can genuinely run at once. Multiple cores can execute tasks in parallel; a single core can instead interleave tasks through context switching, which takes time. section 1.1.1

Figure 1.1. Two approaches to concurrency: parallel execution on a dual-core machine versus task switching on a single-core machine

Compare two tasks running on separate cores with the same tasks interleaved on one core. section 1.1.1

In C++, std::thread::hardware_concurrency() provides a runtime hint about how many threads can run concurrently. It may return 0 when the information is unavailable, so provide a fallback. section 2.4

Use the hint when sizing parallel work, not as a guarantee of performance. Limit thread counts to avoid oversubscription and context-switching costs; even available hardware concurrency does not make every workload worth parallelizing. section 2.4 section 1.2.3

Listing 2.9. A naïve parallel version of std::accumulate
template<typename Iterator,typename T>
struct accumulate_block
{
    void operator()(Iterator first,Iterator last,T& result)
    {
        result=std::accumulate(first,last,result);
    }
};
template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
    unsigned long const length=std::distance(first,last);
    if(!length)
        return init;
    unsigned long const min_per_thread=25;
    unsigned long const max_threads=
        (length+min_per_thread-1)/min_per_thread;
    unsigned long const hardware_threads=
        std::thread::hardware_concurrency();
    unsigned long const num_threads=
        std::min(hardware_threads!=0?hardware_threads:2,max_threads);
    unsigned long const block_size=length/num_threads;
    std::vector<T> results(num_threads);
    std::vector<std::thread>  threads(num_threads-1);
    Iterator block_start=first;
    for(unsigned long i=0;i<(num_threads-1);++i)
    {
        Iterator block_end=block_start;
        std::advance(block_end,block_size);
        threads[i]=std::thread(
            accumulate_block<Iterator,T>(),
            block_start,block_end,std::ref(results[i]));
        block_start=block_end;
    }
    accumulate_block<Iterator,T>()(
        block_start,last,results[num_threads-1]);

    for(auto& entry: threads)
           entry.join();
    return std::accumulate(results.begin(),results.end(),init);
}

A parallel accumulate example uses the hint alongside a minimum block size to choose a thread count. section 2.4

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Defines genuine parallel execution and explains runtime thread-count sizing, but defers exception handling in the parallel algorithm.
Examples
Contrasts two tasks on a dual-core machine with task switching on one core, then sizes threads for parallel accumulation.
What you can do after
Apply std::thread::hardware_concurrency() as a hint when capping threads for parallel work, with a fallback if it returns zero.
How it connects
Connects parallel execution and hardware threads with context switching, oversubscription, and thread pools.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.