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 | |