1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
use std::borrow::Borrow;
pub use std::{
self, any, borrow::Cow, default::Default, iter::FromIterator, option::Option::*, sync::OnceLock,
};
pub use crate::{
bench::BenchOptions,
entry::{
BenchEntry, EntryConst, EntryList, EntryLocation, EntryMeta, EntryType, GenericBenchEntry,
GroupEntry, BENCH_ENTRIES, GROUP_ENTRIES,
},
time::IntoDuration,
};
/// Used by `#[divan::bench(threads = ...)]` to leak thread counts for easy
/// global usage in [`BenchOptions::threads`].
///
/// This enables the `threads` option to be polymorphic over:
/// - `usize`
/// - `bool`
/// - `true` is 0
/// - `false` is 1
/// - Iterators:
/// - `[usize; N]`
/// - `&[usize; N]`
/// - `&[usize]`
///
/// # Orphan Rules Hack
///
/// Normally we can't implement a trait over both `usize` and `I: IntoIterator`
/// because the compiler has no guarantee that `usize` will never implement
/// `IntoIterator`. Ideally we would handle this with specialization, but that's
/// not stable.
///
/// The solution here is to make `IntoThreads` generic to implement technically
/// different traits for `usize` and `IntoIterator` because of different `IMP`
/// values. We then call verbatim `IntoThreads::into_threads(val)` and have the
/// compiler infer the generic parameter for the single `IntoThreads`
/// implementation.
///
/// It's fair to assume that scalar primitives will never implement
/// `IntoIterator`, so this hack shouldn't break in the future 🤠.
pub trait IntoThreads<const IMP: u32> {
fn into_threads(self) -> Cow<'static, [usize]>;
}
impl IntoThreads<0> for usize {
#[inline]
fn into_threads(self) -> Cow<'static, [usize]> {
let counts = match self {
0 => &[0],
1 => &[1],
2 => &[2],
_ => return Cow::Owned(vec![self]),
};
Cow::Borrowed(counts)
}
}
impl IntoThreads<0> for bool {
#[inline]
fn into_threads(self) -> Cow<'static, [usize]> {
let counts = if self {
// Available parallelism.
&[0]
} else {
// No parallelism.
&[1]
};
Cow::Borrowed(counts)
}
}
impl<I> IntoThreads<1> for I
where
I: IntoIterator,
I::Item: Borrow<usize>,
{
#[inline]
fn into_threads(self) -> Cow<'static, [usize]> {
let mut options: Vec<usize> = self.into_iter().map(|i| *i.borrow()).collect();
options.sort_unstable();
options.dedup();
Cow::Owned(options)
}
}
/// Used by `#[divan::bench(counters = [...])]`.
#[inline]
pub fn new_counter_set() -> crate::counter::CounterSet {
Default::default()
}
/// Used by `#[divan::bench]` to truncate arrays for generic `const` benchmarks.
pub const fn shrink_array<T, const IN: usize, const OUT: usize>(
array: [T; IN],
) -> Option<[T; OUT]> {
use std::mem::ManuallyDrop;
#[repr(C)]
union Transmute<F, I> {
from: ManuallyDrop<F>,
into: ManuallyDrop<I>,
}
let from = ManuallyDrop::new(array);
if OUT <= IN {
Some(unsafe { ManuallyDrop::into_inner(Transmute { from }.into) })
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn into_threads() {
macro_rules! test {
($value:expr, $expected:expr) => {
assert_eq!(IntoThreads::into_threads($value).as_ref(), $expected);
};
}
test!(true, &[0]);
test!(false, &[1]);
test!(0, &[0]);
test!(1, &[1]);
test!(42, &[42]);
test!([0; 0], &[]);
test!([0], &[0]);
test!([0, 0], &[0]);
test!([0, 2, 3, 1], &[0, 1, 2, 3]);
test!([0, 0, 2, 3, 2, 1, 3], &[0, 1, 2, 3]);
}
#[test]
fn shrink_array() {
let values = [1, 2, 3, 4, 5];
let equal: Option<[i32; 5]> = super::shrink_array(values);
assert_eq!(equal, Some(values));
let smaller: Option<[i32; 3]> = super::shrink_array(values);
assert_eq!(smaller, Some([1, 2, 3]));
let larger: Option<[i32; 100]> = super::shrink_array(values);
assert_eq!(larger, None);
}
}