Closed hcpl closed 6 years ago
There are some cases with regressions, which are all short tuples:
shrink_i64_1_tuple 461 517 56 12.15% x 0.89
shrink_i64_2_tuple 896 912 16 1.79% x 0.98
shrink_i64_3_tuple 1,550 1,597 47 3.03% x 0.97
shrink_string_1_tuple 250,662 258,711 8,049 3.21% x 0.97
shrink_u64_1_tuple 302 334 32 10.60% x 0.90
shrink_vec_u8_1_tuple 72,397 84,544 12,147 16.78% x 0.86
shrink_vec_u8_2_tuple 437,995 468,957 30,962 7.07% x 0.93
That's due to how the previous implementation was written:
fn shrink(&self)
-> Box<Iterator<Item=($type_a, $($type_n),*)>> {
let (ref $var_a, $(ref $var_n),*) = *self;
let sa = $var_a.shrink().scan(
($($var_n.clone(),)*),
|&mut ($(ref $var_n,)*), $var_a|
Some(($var_a, $($var_n.clone(),)*))
);
let srest = ($($var_n.clone(),)*).shrink()
.scan($var_a.clone(), |$var_a, ($($var_n,)*)|
Some(($var_a.clone(), $($var_n,)*))
);
Box::new(sa.chain(srest))
}
For 1-tuple this expands to
fn shrink(&self)
-> Box<Iterator<Item=(A,)>> {
let (ref a,) = *self;
let sa = a.shrink().scan(
(),
|&mut (), a|
Some((a,))
);
let srest = ().shrink()
.scan(a.clone(), |a, ()|
Some((a.clone(),))
);
Box::new(sa.chain(srest))
}
... this translates to:
fn shrink(&self) -> Box<Iterator<Item=(A,)>> {
let sa = self.0.shrink();
let srest = empty_shrinker();
Box::new(sa.chain(srest))
}
... which doesn't even have any cloning in the first place.
For comparison, the implementation proposed in this PR works like this:
fn shrink(&self) -> Box<Iterator<Item = (A,)>> {
let cloned_for_0 = self.clone();
Box::new(
::std::iter::empty()
.chain(self.0.shrink().map(move |shr_value| {
let mut result = cloned_for_0.clone();
result.0 = shr_value;
result
})),
)
}
So the question is: should I special-case for 1-tuples (and maybe 2- and 3- as well) or it's not worth the efforts and this will cause maintenance burden.
Also if this library focuses on performance for shorter cases (which I guess are much more often needed, because functions tend to have few arguments and they rely on tuples) and readability is less a concern, feel free to close this PR.
Thanks! The performance result of this isn't too concerning to me. This library really wasn't implemented with performance in mind, and cloning is used with reckless abandon. This can definitely be a pain point in some cases, but I more often hit cases where testing the property itself takes a bit of time and causes the overall test to be a bit lengthy.
With that said, it seems like this PR simplifies the code, which is the win I care about, so I'll take it. :-)
This implementation relies less on cloning while shrinking, which is a performance boost, evident from benchmarks:
Also makes macros more readable (I hope so).