Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macro bench_specializations #786

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ harness = false
[[bench]]
name = "powerset"
harness = false

[[bench]]
name = "specializations"
harness = false
106 changes: 1 addition & 105 deletions benches/bench1.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use itertools::free::cloned;
use itertools::iproduct;
use itertools::Itertools;
use itertools::Position;
use itertools::{iproduct, EitherOrBoth};

use std::cmp;
use std::iter::repeat;
Expand Down Expand Up @@ -391,25 +390,6 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) {
});
}

fn ziplongest(c: &mut Criterion) {
let v1 = black_box((0..768).collect_vec());
let v2 = black_box((0..1024).collect_vec());
c.bench_function("ziplongest", move |b| {
b.iter(|| {
let zip = v1.iter().zip_longest(v2.iter());
let sum = zip.fold(0u32, |mut acc, val| {
match val {
EitherOrBoth::Both(x, y) => acc += x * y,
EitherOrBoth::Left(x) => acc += x,
EitherOrBoth::Right(y) => acc += y,
}
acc
});
sum
})
});
}

fn group_by_lazy_1(c: &mut Criterion) {
let mut data = vec![0; 1024];
for (index, elt) in data.iter_mut().enumerate() {
Expand Down Expand Up @@ -448,25 +428,6 @@ fn group_by_lazy_2(c: &mut Criterion) {
});
}

fn while_some(c: &mut Criterion) {
c.bench_function("while_some", |b| {
b.iter(|| {
let data = black_box(
(0..)
.fuse()
.map(|i| std::char::from_digit(i, 16))
.while_some(),
);
// let result: String = data.fold(String::new(), |acc, ch| acc + &ch.to_string());
let result = data.fold(String::new(), |mut acc, ch| {
acc.push(ch);
acc
});
assert_eq!(result.as_str(), "0123456789abcdef");
});
});
}

fn slice_chunks(c: &mut Criterion) {
let data = vec![0; 1024];

Expand Down Expand Up @@ -703,22 +664,6 @@ fn cartesian_product_iterator(c: &mut Criterion) {
});
}

fn cartesian_product_fold(c: &mut Criterion) {
let xs = vec![0; 16];

c.bench_function("cartesian product fold", move |b| {
b.iter(|| {
let mut sum = 0;
iproduct!(&xs, &xs, &xs).fold((), |(), (&x, &y, &z)| {
sum += x;
sum += y;
sum += z;
});
sum
})
});
}

fn multi_cartesian_product_iterator(c: &mut Criterion) {
let xs = [vec![0; 16], vec![0; 16], vec![0; 16]];

Expand All @@ -735,22 +680,6 @@ fn multi_cartesian_product_iterator(c: &mut Criterion) {
});
}

fn multi_cartesian_product_fold(c: &mut Criterion) {
let xs = [vec![0; 16], vec![0; 16], vec![0; 16]];

c.bench_function("multi cartesian product fold", move |b| {
b.iter(|| {
let mut sum = 0;
xs.iter().multi_cartesian_product().fold((), |(), x| {
sum += x[0];
sum += x[1];
sum += x[2];
});
sum
})
});
}

fn cartesian_product_nested_for(c: &mut Criterion) {
let xs = vec![0; 16];

Expand Down Expand Up @@ -839,33 +768,6 @@ fn permutations_slice(c: &mut Criterion) {
});
}

fn with_position_fold(c: &mut Criterion) {
let v = black_box((0..10240).collect_vec());
c.bench_function("with_position fold", move |b| {
b.iter(|| {
v.iter()
.with_position()
.fold(0, |acc, (pos, &item)| match pos {
Position::Middle => acc + item,
Position::First => acc - 2 * item,
Position::Last => acc + 2 * item,
Position::Only => acc + 5 * item,
})
})
});
}

fn tuple_combinations_fold(c: &mut Criterion) {
let v = black_box((0..64).collect_vec());
c.bench_function("tuple_combinations fold", move |b| {
b.iter(|| {
v.iter()
.tuple_combinations()
.fold(0, |acc, (a, b, c, d)| acc + *a * *c - *b * *d)
})
});
}

criterion_group!(
benches,
slice_iter,
Expand All @@ -887,7 +789,6 @@ criterion_group!(
zipdot_i32_unchecked_counted_loop,
zipdot_f32_unchecked_counted_loop,
zip_unchecked_counted_loop3,
ziplongest,
group_by_lazy_1,
group_by_lazy_2,
slice_chunks,
Expand All @@ -903,18 +804,13 @@ criterion_group!(
step_range_2,
step_range_10,
cartesian_product_iterator,
cartesian_product_fold,
multi_cartesian_product_iterator,
multi_cartesian_product_fold,
cartesian_product_nested_for,
all_equal,
all_equal_for,
all_equal_default,
permutations_iter,
permutations_range,
permutations_slice,
with_position_fold,
tuple_combinations_fold,
while_some,
);
criterion_main!(benches);
176 changes: 176 additions & 0 deletions benches/specializations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use criterion::black_box;
use itertools::iproduct;
use itertools::Itertools;

/// Create multiple functions each defining a benchmark group about iterator methods.
///
/// Each created group has functions with the following ids:
///
/// - `next`, `size_hint`, `count`, `last`, `nth`, `collect`, `fold`
/// - and when marked as `DoubleEndedIterator`: `next_back`, `rfold`
/// - and when marked as `ExactSizeIterator`: `len`
///
/// Note that this macro can be called only once.
macro_rules! bench_specializations {
(
$(
$name:ident {
$($extra:ident)*
{$(
$init:stmt;
)*}
$iterator:expr
}
)*
) => {
$(
fn $name(c: &mut ::criterion::Criterion) {
let mut bench_group = c.benchmark_group(stringify!($name));
$(
$init
)*
let bench_first_its = {
let mut bench_idx = 0;
[0; 1000].map(|_| {
let mut it = $iterator;
if bench_idx != 0 {
it.nth(bench_idx - 1);
}
bench_idx += 1;
it
})
};
bench_specializations!(@Iterator bench_group bench_first_its: $iterator);
$(
bench_specializations!(@$extra bench_group bench_first_its: $iterator);
)*
bench_group.finish();
}
)*

::criterion::criterion_group!(benches, $($name, )*);
::criterion::criterion_main!(benches);
};

(@Iterator $group:ident $first_its:ident: $iterator:expr) => {
$group.bench_function("next", |bencher| bencher.iter(|| {
let mut it = $iterator;
while let Some(x) = it.next() {
black_box(x);
}
}));
$group.bench_function("size_hint", |bencher| bencher.iter(|| {
$first_its.iter().for_each(|it| {
black_box(it.size_hint());
})
}));
$group.bench_function("count", |bencher| bencher.iter(|| {
$iterator.count()
}));
$group.bench_function("last", |bencher| bencher.iter(|| {
$iterator.last()
}));
$group.bench_function("nth", |bencher| bencher.iter(|| {
for start in 0_usize..10 {
for n in 0..10 {
let mut it = $iterator;
if let Some(s) = start.checked_sub(1) {
black_box(it.nth(s));
}
Philippe-Cholet marked this conversation as resolved.
Show resolved Hide resolved
while let Some(x) = it.nth(n) {
black_box(x);
}
}
}
}));
$group.bench_function("collect", |bencher| bencher.iter(|| {
$iterator.collect::<Vec<_>>()
}));
$group.bench_function("fold", |bencher| bencher.iter(|| {
$iterator.fold((), |(), x| {
black_box(x);
})
}));
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also incllude all (such as the specialization tests)?

Copy link
Member Author

@Philippe-Cholet Philippe-Cholet Oct 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I listed the currently missing (stable) methods:

Iterator:

  • Depends on fold by default: for_each partition reduce max min max_by_key max_by min_by_key min_by.
  • Depends on try_fold by default: all any find find_map position cmp partial_cmp eq ne lt le gt ge.
  • Depends on try_rfold by default: rposition.

DoubleEndedIterator:

  • Depends on try_rfold by default: rfind.

I'm inclined to not add them at the moment as they rely on one of [try_][r]fold.
Obviously, we can change our mind later.


(@DoubleEndedIterator $group:ident $_first_its:ident: $iterator:expr) => {
$group.bench_function("next_back", |bencher| bencher.iter(|| {
let mut it = $iterator;
while let Some(x) = it.next_back() {
black_box(x);
}
}));
$group.bench_function("nth_back", |bencher| bencher.iter(|| {
for start in 0_usize..10 {
for n in 0..10 {
let mut it = $iterator;
if let Some(s) = start.checked_sub(1) {
black_box(it.nth_back(s));
}
while let Some(x) = it.nth_back(n) {
black_box(x);
}
}
}
}));
$group.bench_function("rfold", |bencher| bencher.iter(|| {
$iterator.rfold((), |(), x| {
black_box(x);
})
}));
};

(@ExactSizeIterator $group:ident $first_its:ident: $_iterator:expr) => {
$group.bench_function("len", |bencher| bencher.iter(|| {
$first_its.iter().for_each(|it| {
black_box(it.len());
})
}));
};
}

// Example: To bench only `ZipLongest::fold`, you can do
// cargo bench --bench specializations zip_longest/fold
bench_specializations! {
cartesian_product {
{
let v = black_box(vec![0; 16]);
}
iproduct!(&v, &v, &v)
}
multi_cartesian_product {
{
let vs = black_box([0; 3].map(|_| vec![0; 16]));
}
vs.iter().multi_cartesian_product()
}
tuple_combinations {
{
let v = black_box((0..64).collect_vec());
}
v.iter().tuple_combinations::<(_, _, _, _)>()
}
while_some {
{}
(0..)
.map(black_box)
.map(|i| char::from_digit(i, 16))
.while_some()
}
with_position {
ExactSizeIterator
{
let v = black_box((0..10240).collect_vec());
}
v.iter().with_position()
}
zip_longest {
DoubleEndedIterator
ExactSizeIterator
Comment on lines +168 to +169
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat idea! Maybe we should do this in specialization tests, too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it too.
ExactSizeIterator might be unnecessary since we are I think unlikely to specialize len but it was easy to add once DoubleEndedIterator was added.

{
let xs = black_box(vec![0; 1024]);
let ys = black_box(vec![0; 768]);
}
xs.iter().zip_longest(ys.iter())
}
}