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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Multi-threaded benchmark execution framework for performance testing.
//!
//! This package provides utilities to execute multi-threaded benchmarks with precise control
//! over thread groups, state management, and measurement timing. It is designed to integrate
//! with benchmarking frameworks like Criterion while handling the complexities of coordinated
//! multi-threaded execution.
//!
//! The core functionality includes:
//! - [`Run`] - Configurable multi-threaded benchmark execution with builder pattern API
//! - [`ThreadPool`] - Pre-warmed thread pool to eliminate thread creation overhead in benchmarks
//! - [`RunMeta`] - Metadata about the benchmark run, including group information and iteration counts
//! - [`RunSummary`] - Results from benchmark execution, including timing and measurement data
//!
//! This package is not meant for use in production, serving only as a development tool for
//! benchmarking and performance analysis.
//!
//! # Operating principles
//!
//! ## Thread groups
//!
//! Benchmarks can divide threads into equal-sized groups, allowing for scenarios where different
//! groups perform different roles (e.g., readers vs writers, producers vs consumers). Each thread
//! receives metadata about which group it belongs to and can behave differently based on this.
//!
//! ## State management
//!
//! The framework supports multiple levels of state:
//! - **Thread State**: Created once per thread, shared across all iterations
//! - **Iteration State**: Created for each iteration, allowing per-iteration setup
//! - **Cleanup State**: Returned by iteration functions, dropped after measurement
//!
//! ## Measurement timing
//!
//! Measurement wrappers allow precise control over what gets measured. The framework separates
//! preparation (unmeasured) from execution (measured) phases, ensuring benchmarks capture only
//! the intended work.
//!
//! # Basic example
//!
//! ```
//! use std::sync::Arc;
//! use std::sync::atomic::{AtomicU64, Ordering};
//!
//! use many_cpus::SystemHardware;
//! use par_bench::{Run, ThreadPool};
//!
//! # fn main() {
//! // Create a thread pool with default processor set
//! let mut pool = ThreadPool::new(&SystemHardware::current().processors());
//!
//! // Shared counter for all threads to increment
//! let counter = Arc::new(AtomicU64::new(0));
//!
//! let run = Run::new()
//! .prepare_thread({
//! let counter = Arc::clone(&counter);
//! move |_| Arc::clone(&counter)
//! })
//! .prepare_iter(|args| Arc::clone(args.thread_state()))
//! .iter(|mut args| {
//! // This is the measured work
//! args.iter_state().fetch_add(1, Ordering::Relaxed);
//! });
//!
//! // Execute 1000 iterations across all threads
//! let results = run.execute_on(&mut pool, 1000);
//! println!("Mean duration: {:?}", results.mean_duration());
//! # }
//! ```
//!
//! # Multi-group example
//!
//! ```
//! use std::sync::Arc;
//! use std::sync::atomic::{AtomicU64, Ordering};
//!
//! use many_cpus::SystemHardware;
//! use new_zealand::nz;
//! use par_bench::{Run, ThreadPool};
//!
//! # fn main() {
//! # if let Some(processors) = SystemHardware::current().processors().to_builder().take(nz!(4)) {
//! let mut pool = ThreadPool::new(&processors);
//!
//! let reader_count = Arc::new(AtomicU64::new(0));
//! let writer_count = Arc::new(AtomicU64::new(0));
//!
//! let run = Run::new()
//! .groups(nz!(2)) // Divide 4 threads into 2 groups of 2 threads each
//! .prepare_thread({
//! let reader_count = Arc::clone(&reader_count);
//! let writer_count = Arc::clone(&writer_count);
//! move |args| {
//! if args.meta().group_index() == 0 {
//! ("reader", Arc::clone(&reader_count))
//! } else {
//! ("writer", Arc::clone(&writer_count))
//! }
//! }
//! })
//! .prepare_iter(|args| args.thread_state().clone())
//! .iter(|mut args| {
//! let (role, counter) = args.take_iter_state();
//! match role {
//! "reader" => {
//! // Reader work
//! counter.fetch_add(1, Ordering::Relaxed);
//! }
//! "writer" => {
//! // Writer work
//! counter.fetch_add(10, Ordering::Relaxed);
//! }
//! _ => unreachable!(),
//! }
//! });
//!
//! let results = run.execute_on(&mut pool, 100);
//! println!("Results: {:?}", results.mean_duration());
//! # }
//! # }
//! ```
//!
//! # Resource usage tracking
//! extension trait becomes available, providing convenient resource usage tracking for benchmarks:
//!
//! ```ignore
//! use alloc_tracker::{Allocator, Session as AllocSession};
//! use all_the_time::Session as TimeSession;
//! use par_bench::{ResourceUsageExt, Run, ThreadPool};
//!
//! #[global_allocator]
//! static ALLOCATOR: Allocator<std::alloc::System> = Allocator::system();
//!
//! let allocs = AllocSession::new();
//! let processor_time = TimeSession::new();
//! let mut pool = ThreadPool::new(&SystemHardware::current().processors().take_all().unwrap());
//!
//! let results = Run::new()
//! .measure_resource_usage(|measure| {
//! measure
//! .allocs(&allocs, "my_operation")
//! .processor_time(&processor_time, "my_operation")
//! })
//! .iter(|_| {
//! let _data = vec![1, 2, 3, 4, 5]; // This allocates memory
//!
//! // Perform processor-intensive work
//! let mut sum = 0;
//! for i in 0..1000 {
//! sum += i * i;
//! }
//! std::hint::black_box(sum);
//! })
//! .execute_on(&mut pool, 1000);
//!
//! // Access the combined resource usage data
//! for output in results.measure_outputs() {
//! if let Some(alloc_report) = output.allocs() {
//! println!("Allocation data available");
//! }
//! if let Some(time_report) = output.processor_time() {
//! println!("Processor time data available");
//! }
//! }
//! ```
//!
//! You can also use just one type of measurement:
//!
//! ```ignore
//! // Just allocation tracking
//! let results = Run::new()
//! .measure_resource_usage(|measure| {
//! measure.allocs(&allocs, "alloc_only")
//! })
//! .iter(|_| { /* work */ })
//! .execute_on(&mut pool, 1000);
//!
//! // Just processor time tracking
//! let results = Run::new()
//! .measure_resource_usage(|measure| {
//! measure.processor_time(&processor_time, "time_only")
//! })
//! .iter(|_| { /* work */ })
//! .execute_on(&mut pool, 1000);
//! ```
// These are in a separate module because 99% of the time the user never needs to name
// these types, so it makes sense to de-emphasize them in the API documentation.
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;