parallel_reduce reduces an input range in parallel and returns one final value.
It is useful when you have code like this:
int sum = 0;
for (int value : values)
{
sum += value;
}and you want Vix to split the work across worker threads safely.
The recommended include is:
#include <vix/threadpool.hpp>#include <iostream>
#include <vector>
#include <vix/threadpool.hpp>
int main()
{
vix::threadpool::ThreadPool pool(4);
std::vector<int> values{1, 2, 3, 4};
const int sum =
vix::threadpool::parallel_reduce(
pool,
values,
0,
[](int current, int value)
{
return current + value;
});
std::cout << "sum: " << sum << '\n'; // 10
pool.shutdown();
return 0;
}- Receives an input range
- Splits it into chunks
- Submits chunk tasks to the pool
- Reduces each chunk locally
- Waits for all chunk tasks
- Combines partial results on the caller thread
- Returns the final value
Container overload (simplest):
vix::threadpool::parallel_reduce(pool, container, initial, reduce);Iterator overload:
vix::threadpool::parallel_reduce(pool, first, last, initial, reduce);The reducer receives the current accumulator and the current value, and returns the next accumulator:
[](int current, int value) { return current + value; }
// For strings:
[](std::string current, const std::string &value) { return current + value; }
// For custom objects:
[](Stats current, const Record &record)
{
current.count += 1;
current.total += record.value;
return current;
}The initial value is used as the starting value for each chunk and when combining partial results:
// Sum:
vix::threadpool::parallel_reduce(pool, values, 0, reduce);
// Product:
const int product =
vix::threadpool::parallel_reduce(
pool, values, 1,
[](int current, int value) { return current * value; });If the input range is empty, parallel_reduce returns the initial value:
std::vector<int> values;
const int result = vix::threadpool::parallel_reduce(pool, values, 42, reduce);
result; // 42Useful when you only want to reduce part of a container:
const int partial =
vix::threadpool::parallel_reduce(
pool,
values.begin() + 2,
values.end(),
0,
[](int current, int value) { return current + value; });parallel_reduce splits the input into chunks. Each chunk becomes one submitted task, computes a partial result, and the caller combines partial results at the end:
input: 1000 elements, chunk size: 250
chunks: 0..250 → partial 1
250..500 → partial 2
500..750 → partial 3
750..1000 → partial 4
↓
final resultIf no chunk size is provided, Vix computes one automatically based on input size, worker count, and target chunk count.
vix::threadpool::ParallelReduceOptions options;
options.chunk_size = 128;
// Or:
auto options = vix::threadpool::ParallelReduceOptions::with_chunk_size(128);| Work per element | Recommended chunk size |
|---|---|
| Cheap | 1024 – 4096 |
| Medium | 128 – 256 |
| Expensive | 1 – 64 |
Small chunks → better load balancing, more overhead. Large chunks → less overhead, less load balancing.
ParallelReduceOptions contains task_options applied to every chunk task:
vix::threadpool::ParallelReduceOptions options;
options.chunk_size = 256;
options.task_options.set_priority(vix::threadpool::TaskPriority::high);
options.task_options.set_timeout(vix::threadpool::Timeout::milliseconds(100));
const int result =
vix::threadpool::parallel_reduce(pool, values, 0, reduce, options);Priority affects chunk ordering while queued. Timeouts are observational.
If one chunk throws, parallel_reduce still waits for all submitted chunks to finish, then rethrows the first captured exception:
try
{
const int result =
vix::threadpool::parallel_reduce(
pool, values, 0,
[](int current, int value)
{
if (value == 4) throw std::runtime_error{"reduce failed"};
return current + value;
});
}
catch (const std::exception &e)
{
std::cout << "parallel_reduce failed: " << e.what() << '\n';
}For best results, the reducer should be associative. Parallel reduction may group values differently from a sequential loop.
Good (associative): +, *, min, max.
Less ideal (non-associative): -, /.
Floating-point addition is not perfectly associative due to rounding. parallel_reduce with double may not produce the exact same bit-level result as a sequential loop. This is normal.
struct Stats { int count{0}; int total{0}; };
Stats stats =
vix::threadpool::parallel_reduce(
pool, values, Stats{},
[](Stats current, int value)
{
current.count += 1;
current.total += value;
return current;
});The reducer runs concurrently in chunk tasks. The safest reducer does not mutate shared state:
// Good:
[](int current, int value) { return current + value; }
// Bad (data race):
int shared = 0;
[&shared](int current, int value) { shared += value; return current + value; }Convenient for examples, but prefer reusing a pool in production:
// Convenient but creates a new pool each time:
const int sum = vix::threadpool::parallel_reduce(values, 0, reduce);
// Better — reuse the pool:
vix::threadpool::ThreadPool pool(4);
auto a = vix::threadpool::parallel_reduce(pool, valuesA, 0, reduce);
auto b = vix::threadpool::parallel_reduce(pool, valuesB, 0, reduce);
pool.shutdown();const int sum =
vix::threadpool::parallel::reduce(pool, values, 0, reduce);#include <iostream>
#include <numeric>
#include <vector>
#include <vix/threadpool.hpp>
int main()
{
vix::threadpool::ThreadPool pool(4);
std::vector<int> values(100);
std::iota(values.begin(), values.end(), 1);
vix::threadpool::ParallelReduceOptions options;
options.chunk_size = 10;
options.task_options.set_priority(vix::threadpool::TaskPriority::high);
const int sum =
vix::threadpool::parallel_reduce(
pool, values, 0,
[](int current, int value) { return current + value; },
options);
std::cout << "sum: " << sum << '\n'; // 5050
pool.shutdown();
return 0;
}Each chunk is submitted as one task. Metrics count chunks, not input elements:
// 1000 elements, chunk size 100 → ~10 chunk tasks
const auto metrics = pool.metrics();
std::cout << metrics.completed_tasks << '\n';Use when: you need one final result, each element can be processed independently, the reducer is associative, you want to avoid shared mutable state, and the input is large enough to benefit from parallelism.
Good examples: sum values, count records, compute min/max, aggregate statistics, combine validation results, compute checksums.
Avoid when: the operation depends on strict left-to-right order, the reducer mutates shared state, the operation is not associative, the input is too small, or work per element is too cheap.
For transformations use parallel_map. For index-based writes use parallel_for. For container mutation by reference use parallel_for_each.
// Bad:
int sum = 0;
vix::threadpool::parallel_for_each(pool, values, [&sum](int value) { sum += value; });
// Good:
int sum = vix::threadpool::parallel_reduce(pool, values, 0,
[](int current, int value) { return current + value; });// Unpredictable parallel result:
vix::threadpool::parallel_reduce(pool, values, 0,
[](int current, int value) { return current - value; });
// Predictable:
vix::threadpool::parallel_reduce(pool, values, 0,
[](int current, int value) { return current + value; });parallel_reduce(pool, input, initial, reduce)
splits input into chunks
submits chunk tasks
each chunk reduces locally
waits for futures
combines partial results
rethrows first exception if any
returns final valueThe user writes:
auto result = parallel_reduce(pool, values, initial, reduce);Vix handles chunking, task submission, worker execution, waiting, exception propagation, partial result collection, and final combination.