Why does std::reduce need commutativity?

std::reduce requires both associativity and commutativity. Associativity is clearly needed for a parallel algorithm, since you want to perform the calculation on separate chunks and then combine them.

As for commutativity: According to a reddit post by MSVC STL developer Billy O'Neal, this is required in order to allow vectorization to SIMD instructions:

Commutativity is also necessary to enable vectorization, since the code you want for reduce to come out as something like:

vecRegister = load_contiguous(first);
while (a vector register sized chunk is left) {
    first += packSize;
    vecRegister = add_packed(load_contiguous(first), vecRegister);
}
// combine vecRegister's packed components

etc., which given ints and SSE registers and a * b * c * d * e * f * g * h gives something like (a * e) * (b * f) * (c * g) * (d * h).

Most other languages aren't doing explicit things to make vectorizing their reduction possible. And nothing says we can't add a noncommutative_reduce or something like that in the future if someone comes up with a compelling use case.


The behavior is actually non-deterministic if the operation between the operands is not commutative. "non-deterministic" is not the same as "undefined". Floating point math is not commutative, for example. This is one reason why a call to std::reduce may not be deterministic, because the binary function is applied in an unspecified order.

Refer to this note in the standard:

Note: The difference between reduce and accumulate is that reduce applies binary_op in an unspecified order, which yields a nondeterministic result for non-associative or non-commutative binary_op such as floating-point addition. —end note ]

Tags:

C++

C++17