criterion/plot/gnuplot_backend/
summary.rs1use {
2 super::{
3 debug_script, gnuplot_escape, DARK_BLUE, DEFAULT_FONT, KDE_POINTS, LINEWIDTH, POINT_SIZE,
4 SIZE,
5 },
6 crate::{
7 kde,
8 measurement::ValueFormatter,
9 plot::LinePlotConfig,
10 report::{BenchmarkId, ValueType},
11 stats::univariate::Sample,
12 AxisScale,
13 },
14 criterion_plot::prelude::*,
15 itertools::Itertools,
16 std::{
17 cmp::Ordering,
18 path::{Path, PathBuf},
19 process::Child,
20 },
21};
22
23const NUM_COLORS: usize = 8;
24static COMPARISON_COLORS: [Color; NUM_COLORS] = [
25 Color::Rgb(178, 34, 34),
26 Color::Rgb(46, 139, 87),
27 Color::Rgb(0, 139, 139),
28 Color::Rgb(255, 215, 0),
29 Color::Rgb(0, 0, 139),
30 Color::Rgb(220, 20, 60),
31 Color::Rgb(139, 0, 139),
32 Color::Rgb(0, 255, 127),
33];
34
35impl AxisScale {
36 fn to_gnuplot(self) -> Scale {
37 match self {
38 AxisScale::Linear => Scale::Linear,
39 AxisScale::Logarithmic => Scale::Logarithmic,
40 }
41 }
42}
43
44#[allow(clippy::explicit_counter_loop)]
45pub(crate) fn line_comparison(
46 line_cfg: LinePlotConfig,
47 formatter: &dyn ValueFormatter,
48 title: &str,
49 all_curves: &[&(&BenchmarkId, Vec<f64>)],
50 path: &Path,
51 value_type: ValueType,
52 axis_scale: AxisScale,
53) -> Child {
54 let path = PathBuf::from(path);
55 let mut f = Figure::new();
56
57 let input_suffix = match value_type {
58 ValueType::Bytes => " Size (Bytes)",
59 ValueType::Elements => " Size (Elements)",
60 ValueType::Bits => " Size (Bits)",
61 ValueType::Value => "",
62 };
63
64 f.set(Font(DEFAULT_FONT))
65 .set(SIZE)
66 .configure(Key, |k| {
67 k.set(Justification::Left)
68 .set(Order::SampleText)
69 .set(Position::Outside(Vertical::Top, Horizontal::Right))
70 })
71 .set(Title(format!("{}: Comparison", gnuplot_escape(title))))
72 .configure(Axis::BottomX, |a| {
73 a.set(Label(format!("Input{}", input_suffix)))
74 .set(axis_scale.to_gnuplot())
75 });
76
77 let mut i = 0;
78
79 let (max_id, max) = all_curves
80 .iter()
81 .map(|&(id, data)| (*id, Sample::new(data).mean()))
82 .fold(None, |prev: Option<(&BenchmarkId, f64)>, next| match prev {
83 Some(prev) if prev.1 >= next.1 => Some(prev),
84 _ => Some(next),
85 })
86 .unwrap();
87
88 let mut max_formatted = [max];
89 let unit = (line_cfg.scale)(formatter, max_id, max, max_id, &mut max_formatted);
90
91 f.configure(Axis::LeftY, |a| {
92 a.configure(Grid::Major, |g| g.show())
93 .configure(Grid::Minor, |g| g.hide())
94 .set(Label(format!("Average {} ({})", line_cfg.label, unit)))
95 .set(axis_scale.to_gnuplot())
96 });
97
98 for (key, group) in &all_curves.iter().chunk_by(|&&&(id, _)| &id.function_id) {
102 let mut tuples: Vec<_> = group
103 .map(|&&(id, ref sample)| {
104 let x = id.as_number().unwrap();
107 let mut y = [Sample::new(sample).mean()];
108
109 (line_cfg.scale)(formatter, max_id, max, id, &mut y);
110
111 (x, y[0])
112 })
113 .collect();
114 tuples.sort_by(|&(ax, _), &(bx, _)| ax.partial_cmp(&bx).unwrap_or(Ordering::Less));
115 let (xs, ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
116
117 let function_name = key.as_ref().map(|string| gnuplot_escape(string));
118
119 f.plot(Lines { x: &xs, y: &ys }, |c| {
120 if let Some(name) = function_name {
121 c.set(Label(name));
122 }
123 c.set(LINEWIDTH)
124 .set(LineType::Solid)
125 .set(COMPARISON_COLORS[i % NUM_COLORS])
126 })
127 .plot(Points { x: &xs, y: &ys }, |p| {
128 p.set(PointType::FilledCircle)
129 .set(POINT_SIZE)
130 .set(COMPARISON_COLORS[i % NUM_COLORS])
131 });
132
133 i += 1;
134 }
135
136 debug_script(&path, &f);
137 f.set(Output(path)).draw().unwrap()
138}
139
140pub fn violin(
141 formatter: &dyn ValueFormatter,
142 title: &str,
143 all_curves: &[&(&BenchmarkId, Vec<f64>)],
144 path: &Path,
145 axis_scale: AxisScale,
146) -> Child {
147 let path = PathBuf::from(&path);
148 let all_curves_vec = all_curves.iter().rev().cloned().collect::<Vec<_>>();
149 let all_curves: &[&(&BenchmarkId, Vec<f64>)] = &all_curves_vec;
150
151 let kdes = all_curves
152 .iter()
153 .map(|&(_, sample)| {
154 let (x, mut y) = kde::sweep(Sample::new(sample), KDE_POINTS, None);
155 let y_max = Sample::new(&y).max();
156 for y in y.iter_mut() {
157 *y /= y_max;
158 }
159
160 (x, y)
161 })
162 .collect::<Vec<_>>();
163 let mut xs = kdes.iter().flat_map(|(x, _)| x.iter()).filter(|&&x| x > 0.);
164 let (mut min, mut max) = {
165 let &first = xs.next().unwrap();
166 (first, first)
167 };
168 for &e in xs {
169 if e < min {
170 min = e;
171 } else if e > max {
172 max = e;
173 }
174 }
175 let mut one = [1.0];
176 let unit = formatter.scale_values((min + max) / 2.0, &mut one);
180
181 let tics = || (0..).map(|x| (f64::from(x)) + 0.5);
182 let size = Size(1280, 200 + (25 * all_curves.len()));
183 let mut f = Figure::new();
184 f.set(Font(DEFAULT_FONT))
185 .set(size)
186 .set(Title(format!("{}: Violin plot", gnuplot_escape(title))))
187 .configure(Axis::BottomX, |a| {
188 a.configure(Grid::Major, |g| g.show())
189 .configure(Grid::Minor, |g| g.hide())
190 .set(Range::Limits(0., max * one[0]))
191 .set(Label(format!("Average time ({})", unit)))
192 .set(axis_scale.to_gnuplot())
193 })
194 .configure(Axis::LeftY, |a| {
195 a.set(Label("Input"))
196 .set(Range::Limits(0., all_curves.len() as f64))
197 .set(TicLabels {
198 positions: tics(),
199 labels: all_curves
200 .iter()
201 .map(|&&(id, _)| gnuplot_escape(id.as_title())),
202 })
203 });
204
205 let mut is_first = true;
206 for (i, (x, y)) in kdes.iter().enumerate() {
207 let i = i as f64 + 0.5;
208 let y1: Vec<_> = y.iter().map(|&y| i + y * 0.45).collect();
209 let y2: Vec<_> = y.iter().map(|&y| i - y * 0.45).collect();
210
211 let x: Vec<_> = x.iter().map(|&x| x * one[0]).collect();
212
213 f.plot(FilledCurve { x, y1, y2 }, |c| {
214 if is_first {
215 is_first = false;
216
217 c.set(DARK_BLUE).set(Label("PDF"))
218 } else {
219 c.set(DARK_BLUE)
220 }
221 });
222 }
223 debug_script(&path, &f);
224 f.set(Output(path)).draw().unwrap()
225}