Skip to content

Commit 39c4a0f

Browse files
feat: manage type generics in codspeed instrumentation
1 parent d1130a4 commit 39c4a0f

File tree

5 files changed

+431
-23
lines changed

5 files changed

+431
-23
lines changed

crates/divan_compat/examples/benches/math.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ mod fibonacci {
9191

9292
// Will be ignored in instrumented mode as we do not support type generics yet
9393
// O(n)
94-
#[cfg(not(codspeed))]
9594
#[divan::bench(
9695
types = [BTreeMap<u64, u64>, HashMap<u64, u64>],
9796
args = VALUES,
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use std::{
2+
any::{Any, TypeId},
3+
cmp::Ordering,
4+
mem::ManuallyDrop,
5+
sync::OnceLock,
6+
};
7+
8+
use super::{BenchEntryRunner, GroupEntry};
9+
10+
// use crate::util::sort::natural_cmp;
11+
12+
/// Compile-time entry for a generic benchmark function, generated by
13+
/// `#[divan::bench]`.
14+
///
15+
/// Unlike `BenchEntry`, this is for a specific generic type or `const`.
16+
///
17+
/// Although this type contains trivially-`Copy` data, it *should not* implement
18+
/// `Clone` because the memory address of each instance is used to determine the
19+
/// relative order in `GroupEntry.generic_benches` when sorting benchmarks by
20+
/// location.
21+
pub struct GenericBenchEntry {
22+
/// The associated group, for entry metadata.
23+
pub group: &'static GroupEntry,
24+
25+
/// The benchmarking function.
26+
pub bench: BenchEntryRunner,
27+
28+
/// A generic type.
29+
pub ty: Option<EntryType>,
30+
31+
/// A `const` value and associated data.
32+
pub const_value: Option<EntryConst>,
33+
}
34+
35+
impl GenericBenchEntry {
36+
pub(crate) fn display_name(&self) -> &str {
37+
match (&self.ty, &self.const_value) {
38+
(_, Some(const_value)) => const_value.name(),
39+
(Some(ty), None) => ty.display_name(),
40+
(None, None) => unreachable!(),
41+
}
42+
}
43+
}
44+
45+
/// Generic type instantiation.
46+
pub struct EntryType {
47+
/// [`std::any::type_name`].
48+
get_type_name: fn() -> &'static str,
49+
50+
/// [`std::any::TypeId::of`].
51+
#[allow(dead_code)]
52+
get_type_id: fn() -> TypeId,
53+
}
54+
55+
impl EntryType {
56+
/// Creates an instance for the given type.
57+
pub const fn new<T: Any>() -> Self {
58+
Self {
59+
get_type_name: std::any::type_name::<T>,
60+
get_type_id: TypeId::of::<T>,
61+
}
62+
}
63+
64+
pub(crate) fn raw_name(&self) -> &'static str {
65+
(self.get_type_name)()
66+
}
67+
68+
pub(crate) fn display_name(&self) -> &'static str {
69+
let mut type_name = self.raw_name();
70+
71+
// Remove module components in type name.
72+
while let Some((prev, next)) = type_name.split_once("::") {
73+
// Do not go past generic type boundary.
74+
if prev.contains('<') {
75+
break;
76+
}
77+
type_name = next;
78+
}
79+
80+
type_name
81+
}
82+
}
83+
84+
/// A reference to a `const` as a `&'static T`.
85+
#[allow(dead_code)]
86+
pub struct EntryConst {
87+
/// `&'static T`.
88+
value: *const (),
89+
90+
/// [`PartialOrd::partial_cmp`].
91+
partial_cmp: unsafe fn(*const (), *const ()) -> Option<Ordering>,
92+
93+
/// [`ToString::to_string`].
94+
to_string: unsafe fn(*const ()) -> String,
95+
96+
/// Cached `to_string` result.
97+
cached_string: ManuallyDrop<OnceLock<&'static str>>,
98+
}
99+
100+
// SAFETY: `T: Send + Sync`.
101+
unsafe impl Send for EntryConst {}
102+
unsafe impl Sync for EntryConst {}
103+
104+
#[allow(dead_code)]
105+
impl EntryConst {
106+
/// Creates entry data for a `const` values.
107+
pub const fn new<T>(value: &'static T) -> Self
108+
where
109+
T: PartialOrd + ToString + Send + Sync,
110+
{
111+
unsafe fn partial_cmp<T: PartialOrd>(a: *const (), b: *const ()) -> Option<Ordering> {
112+
T::partial_cmp(&*a.cast(), &*b.cast())
113+
}
114+
115+
unsafe fn to_string<T: ToString>(value: *const ()) -> String {
116+
T::to_string(&*value.cast())
117+
}
118+
119+
Self {
120+
value: value as *const T as *const (),
121+
partial_cmp: partial_cmp::<T>,
122+
to_string: to_string::<T>,
123+
cached_string: ManuallyDrop::new(OnceLock::new()),
124+
}
125+
}
126+
127+
/// [`ToString::to_string`].
128+
#[inline]
129+
pub(crate) fn name(&self) -> &str {
130+
self.cached_string.get_or_init(|| {
131+
// SAFETY: The function is guaranteed to call `T::to_string`.
132+
let string = unsafe { (self.to_string)(self.value) };
133+
134+
Box::leak(string.into_boxed_str())
135+
})
136+
}
137+
}

crates/divan_compat/src/compat/entry.rs renamed to crates/divan_compat/src/compat/entry/mod.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Handpicked stubs from [divan::entry](https://github.com/nvzqz/divan/blob/main/src/entry/mod.rs)
22
//! Necessary to be able to use the [divan::bench](https://docs.rs/divan/0.1.17/divan/attr.bench.html) macro without changing it too much
3+
4+
mod generic;
5+
36
use std::{
47
ptr,
58
sync::{
@@ -13,12 +16,17 @@ use super::{
1316
BenchArgsRunner,
1417
};
1518

19+
pub use generic::{EntryType, GenericBenchEntry};
20+
1621
/// Benchmark entries generated by `#[divan::bench]`.
1722
///
1823
/// Note: generic-type benchmark entries are instead stored in `GROUP_ENTRIES`
1924
/// in `generic_benches`.
2025
pub static BENCH_ENTRIES: EntryList<BenchEntry> = EntryList::root();
2126

27+
/// Group entries generated by `#[divan::bench_group]`.
28+
pub static GROUP_ENTRIES: EntryList<GroupEntry> = EntryList::root();
29+
2230
/// Determines how the benchmark entry is run.
2331
#[derive(Clone, Copy)]
2432
pub enum BenchEntryRunner {
@@ -37,6 +45,36 @@ pub struct BenchEntry {
3745
pub bench: BenchEntryRunner,
3846
}
3947

48+
/// Compile-time entry for a benchmark group, generated by
49+
/// `#[divan::bench_group]` or a generic-type `#[divan::bench]`.
50+
pub struct GroupEntry {
51+
/// Entry metadata.
52+
pub meta: EntryMeta,
53+
54+
/// Generic `#[divan::bench]` entries.
55+
///
56+
/// This is two-dimensional to make code generation simpler. The outer
57+
/// dimension corresponds to types and the inner dimension corresponds to
58+
/// constants.
59+
pub generic_benches: Option<&'static [&'static [GenericBenchEntry]]>,
60+
}
61+
62+
impl GroupEntry {
63+
pub(crate) fn generic_benches_iter(&self) -> impl Iterator<Item = &'static GenericBenchEntry> {
64+
self.generic_benches
65+
.unwrap_or_default()
66+
.iter()
67+
.flat_map(|benches| benches.iter())
68+
}
69+
}
70+
71+
/// `BenchEntry` or `GenericBenchEntry`.
72+
#[derive(Clone, Copy)]
73+
pub(crate) enum AnyBenchEntry<'a> {
74+
Bench(&'a BenchEntry),
75+
GenericBench(&'a GenericBenchEntry),
76+
}
77+
4078
/// Metadata common to `#[divan::bench]` and `#[divan::bench_group]`.
4179
pub struct EntryMeta {
4280
/// The entry's display name.
@@ -152,3 +190,30 @@ impl<T> EntryList<T> {
152190
}
153191
}
154192
}
193+
194+
impl<'a> AnyBenchEntry<'a> {
195+
/// Returns this entry's benchmark runner.
196+
#[inline]
197+
pub fn bench_runner(self) -> &'a BenchEntryRunner {
198+
match self {
199+
Self::Bench(BenchEntry { bench, .. })
200+
| Self::GenericBench(GenericBenchEntry { bench, .. }) => bench,
201+
}
202+
}
203+
204+
#[inline]
205+
pub fn meta(self) -> &'a EntryMeta {
206+
match self {
207+
Self::Bench(entry) => &entry.meta,
208+
Self::GenericBench(entry) => &entry.group.meta,
209+
}
210+
}
211+
212+
#[inline]
213+
pub fn display_name(self) -> &'a str {
214+
match self {
215+
Self::Bench(entry) => entry.meta.display_name,
216+
Self::GenericBench(entry) => entry.display_name(),
217+
}
218+
}
219+
}

crates/divan_compat/src/compat/mod.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
pub mod __private {
44
pub use super::{
55
bench::{BenchArgs, BenchOptions},
6-
entry::{BenchEntry, BenchEntryRunner, EntryList, EntryLocation, EntryMeta, BENCH_ENTRIES},
6+
entry::{
7+
BenchEntry, BenchEntryRunner, EntryList, EntryLocation, EntryMeta, EntryType,
8+
GenericBenchEntry, GroupEntry, BENCH_ENTRIES, GROUP_ENTRIES,
9+
},
710
};
811

912
pub use divan::__private::{Arg, ToStringHelper};
@@ -18,12 +21,25 @@ use std::{cell::RefCell, rc::Rc};
1821

1922
pub use bench::*;
2023
use codspeed::codspeed::CodSpeed;
24+
use entry::AnyBenchEntry;
2125

2226
pub fn main() {
2327
// Outlined steps of original divan::main and their equivalent in codspeed instrumented mode
2428
// 1. Get registered entries
25-
// TODO: Manage bench groups
26-
let bench_entries = &entry::BENCH_ENTRIES;
29+
let group_entries = &entry::GROUP_ENTRIES;
30+
31+
let generic_bench_entries = group_entries.iter().flat_map(|group| {
32+
group
33+
.generic_benches_iter()
34+
.map(AnyBenchEntry::GenericBench)
35+
});
36+
37+
let bench_entries = entry::BENCH_ENTRIES
38+
.iter()
39+
.map(AnyBenchEntry::Bench)
40+
.chain(generic_bench_entries);
41+
42+
// TODO: Manage non generic bench groups
2743

2844
// 2. Build an execution tree
2945
// No need, we do not manage detailed tree printing like original divan, and we extract
@@ -35,25 +51,30 @@ pub fn main() {
3551

3652
// 4. Scan the tree and execute benchmarks
3753
let codspeed = Rc::new(RefCell::new(CodSpeed::new()));
38-
for entry in bench_entries.iter() {
39-
let entry_uri = uri::generate(entry.meta.display_name, &entry.meta);
54+
for entry in bench_entries {
55+
let runner = entry.bench_runner();
56+
let meta = entry.meta();
4057

41-
if let Some(options) = &entry.meta.bench_options.as_ref() {
58+
if let Some(options) = &meta.bench_options {
4259
if let Some(true) = options.ignore {
43-
println!("Skipped: {}", entry_uri);
60+
let uri = uri::generate(&entry, entry.display_name());
61+
println!("Skipped: {}", uri);
4462
continue;
4563
}
4664
}
47-
match entry.bench {
65+
match runner {
4866
entry::BenchEntryRunner::Plain(bench_fn) => {
49-
bench_fn(bench::Bencher::new(&codspeed, entry_uri));
67+
let uri = uri::generate(&entry, entry.display_name());
68+
69+
bench_fn(bench::Bencher::new(&codspeed, uri));
5070
}
5171
entry::BenchEntryRunner::Args(bench_runner) => {
5272
let bench_runner = bench_runner();
5373

5474
for (arg_index, arg_name) in bench_runner.arg_names().iter().enumerate() {
55-
let entry_name_with_arg = uri::append_arg(&entry_uri, arg_name);
56-
let bencher = bench::Bencher::new(&codspeed, entry_name_with_arg);
75+
let uri = uri::generate(&entry, arg_name);
76+
77+
let bencher = bench::Bencher::new(&codspeed, uri);
5778

5879
bench_runner.bench(bencher, arg_index);
5980
}

0 commit comments

Comments
 (0)