Iterator::unzip
not zero cost · Issue #72085 · rust-lang/rust (original) (raw)
In my attempt to move some code to a more functional style I found that the performance regressed significantly. I have narrowed this regression down to the Iterator::unzip
function.
I tried this code:
extern crate test; use test::{Bencher, black_box};
fn run_functional(l: &Vec<(usize, usize)>) -> (Vec, Vec) { l.iter().copied().unzip() }
fn run_imperative(l: &Vec<(usize, usize)>) -> (Vec, Vec) { let len = l.len(); let (mut result1, mut result2) = (Vec::with_capacity(len), Vec::with_capacity(len)); for item in l.iter().copied() { result1.push(item.0); result2.push(item.1); } (result1, result2) }
#[bench] fn bench_functional(b: &mut Bencher) { let list = black_box(vec![(1, 2); 256]); b.iter(|| run_functional(&list)); }
#[bench] fn bench_imperative(b: &mut Bencher) { let list = black_box(vec![(1, 2); 256]); b.iter(|| run_imperative(&list)); }
I expected to see this happen:
I expect these benchmarks to yield the same results, since they both attempt to do the same thing.
Instead, this happened:
The benchmark results are:
test bench_functional ... bench: 1,440 ns/iter (+/- 66)
test bench_imperative ... bench: 443 ns/iter (+/- 43)
From what I can tell from reading the code in the standard library, there are two reasons why the imperative style loop is so much faster. The imperative version uses Vec::push
instead of Extend::extend
. Here push is significantly faster. The rest of the difference comes from the fact that the vectors are initialized using with_capacity
, whereas the functional variant seems to reallocate. I get the same performance when I change my imperative code to this:
fn run_imperative(l: &Vec<(usize, usize)>) -> (Vec, Vec) { let (mut result1, mut result2) = (Vec::new(), Vec::new()); for item in l.iter().copied() { result1.extend(Some(item.0)); result2.extend(Some(item.1)); } (result1, result2) }
Link to the Reddit thread, with a faster but hacky implementation from u/SkiFire13
Meta
I have reproduced this issue on the latest nightly compiler (as of writing):
$ rustc --version --verbose
rustc 1.45.0-nightly (bad3bf622 2020-05-09)
binary: rustc
commit-hash: bad3bf622bded50a97c0a54e29350eada2a3a169
commit-date: 2020-05-09
host: x86_64-unknown-linux-gnu
release: 1.45.0-nightly
LLVM version: 9.0