| 1 | use std::{ |
| 2 | borrow::{Borrow, Cow}, |
| 3 | fmt::Debug, |
| 4 | }; |
| 5 | |
| 6 | pub use crate::{ |
| 7 | benchmark::{BenchArgs, BenchOptions}, |
| 8 | entry::{ |
| 9 | BenchEntry, BenchEntryRunner, EntryConst, EntryList, EntryLocation, EntryMeta, EntryType, |
| 10 | GenericBenchEntry, GroupEntry, BENCH_ENTRIES, GROUP_ENTRIES, |
| 11 | }, |
| 12 | time::IntoDuration, |
| 13 | }; |
| 14 | |
| 15 | /// Helper to convert values to strings via `ToString` or fallback to `Debug`. |
| 16 | /// |
| 17 | /// This works by having a `Debug`-based `ToString::to_string` method that will |
| 18 | /// be chosen if the wrapped type implements `Debug` *but not* `ToString`. If |
| 19 | /// the wrapped type implements `ToString`, then the inherent |
| 20 | /// `ToStringHelper::to_string` method will be chosen instead. |
| 21 | pub struct ToStringHelper<'a, T: 'static>(pub &'a T); |
| 22 | |
| 23 | #[allow (clippy::to_string_trait_impl)] |
| 24 | impl<T: Debug> ToString for ToStringHelper<'_, T> { |
| 25 | #[inline ] |
| 26 | fn to_string(&self) -> String { |
| 27 | format!(" {:?}" , self.0) |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | impl<T: ToString> ToStringHelper<'_, T> { |
| 32 | #[allow (clippy::inherent_to_string)] |
| 33 | #[inline ] |
| 34 | pub fn to_string(&self) -> String { |
| 35 | self.0.to_string() |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | /// Used by `#[divan::bench(args = ...)]` to enable polymorphism. |
| 40 | pub trait Arg<T> { |
| 41 | fn get(this: Self) -> T; |
| 42 | } |
| 43 | |
| 44 | impl<T> Arg<T> for T { |
| 45 | #[inline ] |
| 46 | fn get(this: Self) -> T { |
| 47 | this |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | impl<'a, T: ?Sized> Arg<&'a T> for &'a Cow<'a, T> |
| 52 | where |
| 53 | T: ToOwned, |
| 54 | { |
| 55 | #[inline ] |
| 56 | fn get(this: Self) -> &'a T { |
| 57 | this |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | impl<'a> Arg<&'a str> for &'a String { |
| 62 | #[inline ] |
| 63 | fn get(this: Self) -> &'a str { |
| 64 | this |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | impl<T: Copy> Arg<T> for &T { |
| 69 | #[inline ] |
| 70 | fn get(this: Self) -> T { |
| 71 | *this |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | impl<T: Copy> Arg<T> for &&T { |
| 76 | #[inline ] |
| 77 | fn get(this: Self) -> T { |
| 78 | **this |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | impl<T: Copy> Arg<T> for &&&T { |
| 83 | #[inline ] |
| 84 | fn get(this: Self) -> T { |
| 85 | ***this |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /// Used by `#[divan::bench(threads = ...)]` to leak thread counts for easy |
| 90 | /// global usage in [`BenchOptions::threads`]. |
| 91 | /// |
| 92 | /// This enables the `threads` option to be polymorphic over: |
| 93 | /// - `usize` |
| 94 | /// - `bool` |
| 95 | /// - `true` is 0 |
| 96 | /// - `false` is 1 |
| 97 | /// - Iterators: |
| 98 | /// - `[usize; N]` |
| 99 | /// - `&[usize; N]` |
| 100 | /// - `&[usize]` |
| 101 | /// |
| 102 | /// # Orphan Rules Hack |
| 103 | /// |
| 104 | /// Normally we can't implement a trait over both `usize` and `I: IntoIterator` |
| 105 | /// because the compiler has no guarantee that `usize` will never implement |
| 106 | /// `IntoIterator`. Ideally we would handle this with specialization, but that's |
| 107 | /// not stable. |
| 108 | /// |
| 109 | /// The solution here is to make `IntoThreads` generic to implement technically |
| 110 | /// different traits for `usize` and `IntoIterator` because of different `IMP` |
| 111 | /// values. We then call verbatim `IntoThreads::into_threads(val)` and have the |
| 112 | /// compiler infer the generic parameter for the single `IntoThreads` |
| 113 | /// implementation. |
| 114 | /// |
| 115 | /// It's fair to assume that scalar primitives will never implement |
| 116 | /// `IntoIterator`, so this hack shouldn't break in the future 🤠. |
| 117 | pub trait IntoThreads<const IMP: u32> { |
| 118 | fn into_threads(self) -> Cow<'static, [usize]>; |
| 119 | } |
| 120 | |
| 121 | impl IntoThreads<0> for usize { |
| 122 | #[inline ] |
| 123 | fn into_threads(self) -> Cow<'static, [usize]> { |
| 124 | let counts: &[usize; 1] = match self { |
| 125 | 0 => &[0], |
| 126 | 1 => &[1], |
| 127 | 2 => &[2], |
| 128 | _ => return Cow::Owned(vec![self]), |
| 129 | }; |
| 130 | Cow::Borrowed(counts) |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | impl IntoThreads<0> for bool { |
| 135 | #[inline ] |
| 136 | fn into_threads(self) -> Cow<'static, [usize]> { |
| 137 | let counts: &[usize; 1] = if self { |
| 138 | // Available parallelism. |
| 139 | &[0] |
| 140 | } else { |
| 141 | // No parallelism. |
| 142 | &[1] |
| 143 | }; |
| 144 | Cow::Borrowed(counts) |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | impl<I> IntoThreads<1> for I |
| 149 | where |
| 150 | I: IntoIterator, |
| 151 | I::Item: Borrow<usize>, |
| 152 | { |
| 153 | #[inline ] |
| 154 | fn into_threads(self) -> Cow<'static, [usize]> { |
| 155 | let mut options: Vec<usize> = self.into_iter().map(|i: impl Borrow| *i.borrow()).collect(); |
| 156 | options.sort_unstable(); |
| 157 | options.dedup(); |
| 158 | Cow::Owned(options) |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /// Used by `#[divan::bench(counters = [...])]`. |
| 163 | #[inline ] |
| 164 | pub fn new_counter_set() -> crate::counter::CounterSet { |
| 165 | Default::default() |
| 166 | } |
| 167 | |
| 168 | /// Used by `#[divan::bench]` to truncate arrays for generic `const` benchmarks. |
| 169 | pub const fn shrink_array<T, const IN: usize, const OUT: usize>( |
| 170 | array: [T; IN], |
| 171 | ) -> Option<[T; OUT]> { |
| 172 | use std::mem::ManuallyDrop; |
| 173 | |
| 174 | #[repr (C)] |
| 175 | union Transmute<F, I> { |
| 176 | from: ManuallyDrop<F>, |
| 177 | into: ManuallyDrop<I>, |
| 178 | } |
| 179 | |
| 180 | let from: ManuallyDrop<[T; IN]> = ManuallyDrop::new(array); |
| 181 | |
| 182 | if OUT <= IN { |
| 183 | Some(unsafe { ManuallyDrop::into_inner(slot:Transmute { from }.into) }) |
| 184 | } else { |
| 185 | None |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | #[cfg (test)] |
| 190 | mod tests { |
| 191 | use super::*; |
| 192 | |
| 193 | #[test ] |
| 194 | fn into_threads() { |
| 195 | macro_rules! test { |
| 196 | ($value:expr, $expected:expr) => { |
| 197 | assert_eq!(IntoThreads::into_threads($value).as_ref(), $expected); |
| 198 | }; |
| 199 | } |
| 200 | |
| 201 | test !(true, &[0]); |
| 202 | test !(false, &[1]); |
| 203 | |
| 204 | test !(0, &[0]); |
| 205 | test !(1, &[1]); |
| 206 | test !(42, &[42]); |
| 207 | |
| 208 | test !([0; 0], &[]); |
| 209 | test !([0], &[0]); |
| 210 | test !([0, 0], &[0]); |
| 211 | |
| 212 | test !([0, 2, 3, 1], &[0, 1, 2, 3]); |
| 213 | test !([0, 0, 2, 3, 2, 1, 3], &[0, 1, 2, 3]); |
| 214 | } |
| 215 | |
| 216 | #[test ] |
| 217 | fn shrink_array() { |
| 218 | let values = [1, 2, 3, 4, 5]; |
| 219 | |
| 220 | let equal: Option<[i32; 5]> = super::shrink_array(values); |
| 221 | assert_eq!(equal, Some(values)); |
| 222 | |
| 223 | let smaller: Option<[i32; 3]> = super::shrink_array(values); |
| 224 | assert_eq!(smaller, Some([1, 2, 3])); |
| 225 | |
| 226 | let larger: Option<[i32; 100]> = super::shrink_array(values); |
| 227 | assert_eq!(larger, None); |
| 228 | } |
| 229 | } |
| 230 | |