Основное отличие состоит в том, что версия итератора внутренне использует Iterator::size_hint
, чтобы определить, сколько места нужно зарезервировать в HashSet
перед тем, как собирать в него.Это предотвращает повторную перераспределение по мере роста набора.
Вы можете сделать то же самое, используя HashSet::with_capacity
вместо HashSet::new
:
let mut candies_kinds = HashSet::with_capacity(candies.len());
В моем тесте это единственное изменение делает ваш кодзначительно быстрее, чем итератор.Однако, если я упросту ваш код, чтобы убрать раннюю оптимизацию, он будет работать почти в то же время, что и версия итератора.
pub fn distribute_candies(candies: &[i32]) -> i32 {
let sister_candies = (candies.len() / 2) as i32;
let mut candies_kinds = HashSet::with_capacity(candies.len());
for candy in candies.into_iter() {
candies_kinds.insert(candy);
}
sister_candies.min(candies_kinds.len() as i32)
}
Время:
test tests::bench_iter ... bench: 262,315 ns/iter (+/- 23,704)
test tests::bench_loop ... bench: 307,697 ns/iter (+/- 16,119)
test tests::bench_loop_with_capacity ... bench: 112,194 ns/iter (+/- 18,295)
test tests::bench_loop_with_capacity_no_bailout ... bench: 259,961 ns/iter (+/- 17,712)
Это предполагаетмне, что HashSet
preallocation является доминирующей разницей.Ваша дополнительная оптимизация также оказывается очень эффективной - по крайней мере, с набором данных, который я выбрал.