rayon-rs / rayon

Rayon: A data parallelism library for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Documentation for `ParallelIterator::fold` and/or `ParallelIterator::reduce` seam to be incorrect

BlockListed opened this issue · comments

The documentation for ParallelIterator::fold currently (1.10.0) shows the following examples:

use rayon::prelude::*;

let s =
    ['a', 'b', 'c', 'd', 'e']
    .par_iter()
    .map(|c: &char| format!("{}", c))
    .reduce(|| String::new(),
            |mut a: String, b: String| { a.push_str(&b); a });

assert_eq!(s, "abcde");
use rayon::prelude::*;

let s =
    ['a', 'b', 'c', 'd', 'e']
    .par_iter()
    .fold(|| String::new(),
            |mut s: String, c: &char| { s.push(*c); s })
    .reduce(|| String::new(),
            |mut a: String, b: String| { a.push_str(&b); a });

assert_eq!(s, "abcde");

If the docs for ParallelIterator::reduce are to be believed, then the operation passed to it has to be associative, which String::push_str definitely isn't. Therefore according to my understanding of rayon, at least one of these two is incorrectly documented.

the operation passed to it has to be associative, which String::push_str definitely isn't

Isn't it? If you ignore the identity of the underlying storage, then a.push_str(&b) is just a concatenated with b. It only reuses a's memory to avoid an unnecessary allocation.

In particular, if a, b and c are String, then

a.push_str(&b);
a.push_str(&c);

stores the same value in a as

b.push_str(&c);
a.push_str(&b);

Or put differently, the operation is

fn concat(mut a: String, b: String) -> String {
  a.push_str(&b);
  a
}

which does fulfil

concat(a, concat(b, c)) == concat(concat(a, b), c)

Just realized my issue, I confused associative with commutative. My understanding was that if I had a list [a, b], turned it into a parallel iterator and called reduce on it, it was not determined if the reduction function f(x,y) was called as f(a, b) or f(b, a).

Probably should've clicked on the link in the reduce docs, however the fact that the example for reduce was commutative didn't help my confusion.

We do preserve the order of operands, with the exception of par_bridge().