2

i'm trying to parallelize the following function:

pub fn encode(&self, s: &String) -> String {
        s.chars()
            .par_iter() // error here
            .map(|c| Character::try_from(c))
            .enumerate()
            .map(|(n, c)| match c {
                Ok(plain) => self.encode_at(plain, n).into(),
                Err(e) => match e {
                    ParsingError::Charset(non_alphabetic) => non_alphabetic,
                    _ => unreachable!(),
                },
            })
            .collect()
    }

I get the following error when trying to go from the Chars iterator into a parallel iterator:

the method par_iter exists for struct std::str::Chars<'_>, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
&std::str::Chars<'_>: IntoParallelIterator
which is required by std::str::Chars<'_>: rayon::iter::IntoParallelRefIteratorrustcE0599

I would expect that converting an iterator into a parallel iterator would be fairly trivial but apparently not

Brandon Piña
  • 614
  • 1
  • 6
  • 20
  • I see now that the `.par_bridge()` function exists, but i would prefer not to use it as the ordering of the items matters – Brandon Piña Oct 13 '22 at 23:25
  • 2
    This is not possible. Since a `char` may occupy anywhere between 1 and 4 bytes when encoded in UTF-8, we do not know where to write a char until all prior characters in the sequence have been written. Additionally, I am doubtful that you will see any speed-ups with this approach. Unless the operation you perform on each character is very expensive, it likely that simply transferring the characters to different threads and handling concurrency would be much more expensive than any performance gains you may get. Try splitting the input into large chunks that each thread can handle. – Locke Oct 14 '22 at 01:45
  • I ended up collect()-ing into a vector and the using par_iter on that vector. According to my criterion benchmarks it's slower for strings less than 10k characters but much, much faster for 1million + – Brandon Piña Oct 14 '22 at 01:52
  • @Locke what if I don't need to guarantee that char is valid utf-8? I only care that they are ascii which i believe we can trust is only 8 bytes – Brandon Piña Oct 14 '22 at 01:54
  • 2
    @BrandonPiña If you're fairly convinced that your strings are ASCII-only, you can perhaps try iterating over the `String` byte-by-byte instead of codepoint-by-codepoint. Try `s.as_bytes().par_iter()...` instead of `s.chars().par_iter()...`. – d2718 Oct 14 '22 at 02:11
  • .par_char_indicies() did the trick – Brandon Piña Feb 07 '23 at 03:30

1 Answers1

3

The problem is that characters in UTF-8 have variable size - ASCII characters take one byte but other ones take two to four bytes. This makes splitting up a string for parallel processing problematic, since the middle byte in the string array may not be the actual middle of the string, and may even be in the middle of a character.

That said, that should not make parallel processing impossible. It's not critical that the string be evenly split among workers, and you can find the start or end of a multi-byte character in the middle of a UTF-8 sequence if you know how they are encoded.

So at least in theory you could iterate in parallel over a string. I'm guessing the rayon authors haven't implemented it because it's not a common use case and it's somewhat tricky to do.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85