A serial region is work performed by one thread rather than concurrently by several. In data processing, splitting an input block and writing a completed result are serial tasks surrounding parallel chunk processing. section 4.4.9
A basic barrier can coordinate serial work assigned to thread 0: other threads wait until it finishes preparing chunks, then all process their own chunks and synchronize before thread 0 writes the result. A flex_barrier can instead run a completion function on one arriving thread after all threads arrive. That function can write results and prepare the next block, leaving one synchronization point in the loop. It can also change the number of participants for the next cycle, so the program must ensure the correct number arrive. section 4.4.9 section 4.4.10
Processing data with flex_barrier
A completion function writes the current result, splits the next input block, and returns -1 to keep the participant count unchanged. The worker loop then contains only the parallel processing and a single synchronization point. section 4.4.10
Listing 4.27. Using std::flex_barrier to provide a serial region
void process_data(data_source &source, data_sink &sink) {
unsigned const concurrency = std::thread::hardware_concurrency();
unsigned const num_threads = (concurrency > 0) ? concurrency : 2;
std::vector<data_chunk> chunks;
auto split_source = [&] { #1
if (!source.done()) {
data_block current_block = source.get_next_data_block();
chunks = divide_into_chunks(current_block, num_threads);
}
};
split_source(); #2
result_block result;
std::experimental::flex_barrier sync(num_threads, [&] { #3
sink.write_data(std::move(result));
split_source(); #4
return -1; #5
});
std::vector<joining_thread> threads(num_threads);
for (unsigned i = 0; i < num_threads; ++i) {
threads[i] = joining_thread([&, i] {
while (!source.done()) { #6
result.set_chunk(i, num_threads, process(chunks[i]));
sync.arrive_and_wait(); #7
}
});
}
}
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":6,\"ch\":63},{\"line\":6,\"ch\":65}],[{\"line\":13,\"ch\":63},{\"line\":13,\"ch\":65}],[{\"line\":17,\"ch\":63},{\"line\":17,\"ch\":65}],[{\"line\":19,\"ch\":63},{\"line\":19,\"ch\":65}],[{\"line\":20,\"ch\":63},{\"line\":20,\"ch\":65}],[{\"line\":26,\"ch\":63},{\"line\":26,\"ch\":65}],[{\"line\":28,\"ch\":63},{\"line\":28,\"ch\":65}]]"}
!@%STYLE%@!
See the flex_barrier example, including the extracted block-splitting function and completion function. section 4.4.10
Serial work limits the performance benefit of adding processors: under Amdahl’s law, a one-third serial fraction caps theoretical speedup at three, even with infinitely many processors. section 8.4.2
