Description
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<usize>, Vec<usize>) {
l.iter().copied().unzip()
}
fn run_imperative(l: &Vec<(usize, usize)>) -> (Vec<usize>, Vec<usize>) {
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<usize>, Vec<usize>) {
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