1 use super::*;
2 use crate::AxisScale;
3 use itertools::Itertools;
4 use plotters::coord::{
5 ranged1d::{AsRangedCoord, ValueFormatter as PlottersValueFormatter},
6 Shift,
7 };
8 use std::cmp::Ordering;
9 use std::path::Path;
10
11 const NUM_COLORS: usize = 8;
12 static COMPARISON_COLORS: [RGBColor; NUM_COLORS] = [
13 RGBColor(178, 34, 34),
14 RGBColor(46, 139, 87),
15 RGBColor(0, 139, 139),
16 RGBColor(255, 215, 0),
17 RGBColor(0, 0, 139),
18 RGBColor(220, 20, 60),
19 RGBColor(139, 0, 139),
20 RGBColor(0, 255, 127),
21 ];
22
line_comparison( formatter: &dyn ValueFormatter, title: &str, all_curves: &[&(&BenchmarkId, Vec<f64>)], path: &Path, value_type: ValueType, axis_scale: AxisScale, )23 pub fn line_comparison(
24 formatter: &dyn ValueFormatter,
25 title: &str,
26 all_curves: &[&(&BenchmarkId, Vec<f64>)],
27 path: &Path,
28 value_type: ValueType,
29 axis_scale: AxisScale,
30 ) {
31 let (unit, series_data) = line_comparison_series_data(formatter, all_curves);
32
33 let x_range =
34 plotters::data::fitting_range(series_data.iter().map(|(_, xs, _)| xs.iter()).flatten());
35 let y_range =
36 plotters::data::fitting_range(series_data.iter().map(|(_, _, ys)| ys.iter()).flatten());
37 let root_area = SVGBackend::new(&path, SIZE)
38 .into_drawing_area()
39 .titled(&format!("{}: Comparison", title), (DEFAULT_FONT, 20))
40 .unwrap();
41
42 match axis_scale {
43 AxisScale::Linear => {
44 draw_line_comarision_figure(root_area, unit, x_range, y_range, value_type, series_data)
45 }
46 AxisScale::Logarithmic => draw_line_comarision_figure(
47 root_area,
48 unit,
49 LogRange(x_range),
50 LogRange(y_range),
51 value_type,
52 series_data,
53 ),
54 }
55 }
56
draw_line_comarision_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>( root_area: DrawingArea<SVGBackend, Shift>, y_unit: &str, x_range: XR, y_range: YR, value_type: ValueType, data: Vec<(Option<&String>, Vec<f64>, Vec<f64>)>, ) where XR::CoordDescType: PlottersValueFormatter<f64>, YR::CoordDescType: PlottersValueFormatter<f64>,57 fn draw_line_comarision_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
58 root_area: DrawingArea<SVGBackend, Shift>,
59 y_unit: &str,
60 x_range: XR,
61 y_range: YR,
62 value_type: ValueType,
63 data: Vec<(Option<&String>, Vec<f64>, Vec<f64>)>,
64 ) where
65 XR::CoordDescType: PlottersValueFormatter<f64>,
66 YR::CoordDescType: PlottersValueFormatter<f64>,
67 {
68 let input_suffix = match value_type {
69 ValueType::Bytes => " Size (Bytes)",
70 ValueType::Elements => " Size (Elements)",
71 ValueType::Value => "",
72 };
73
74 let mut chart = ChartBuilder::on(&root_area)
75 .margin((5).percent())
76 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
77 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
78 .build_cartesian_2d(x_range, y_range)
79 .unwrap();
80
81 chart
82 .configure_mesh()
83 .disable_mesh()
84 .x_desc(format!("Input{}", input_suffix))
85 .y_desc(format!("Average time ({})", y_unit))
86 .draw()
87 .unwrap();
88
89 for (id, (name, xs, ys)) in (0..).zip(data.into_iter()) {
90 let series = chart
91 .draw_series(
92 LineSeries::new(
93 xs.into_iter().zip(ys.into_iter()),
94 COMPARISON_COLORS[id % NUM_COLORS].filled(),
95 )
96 .point_size(POINT_SIZE),
97 )
98 .unwrap();
99 if let Some(name) = name {
100 let name: &str = &*name;
101 series.label(name).legend(move |(x, y)| {
102 Rectangle::new(
103 [(x, y - 5), (x + 20, y + 5)],
104 COMPARISON_COLORS[id % NUM_COLORS].filled(),
105 )
106 });
107 }
108 }
109
110 chart
111 .configure_series_labels()
112 .position(SeriesLabelPosition::UpperLeft)
113 .draw()
114 .unwrap();
115 }
116
117 #[allow(clippy::type_complexity)]
line_comparison_series_data<'a>( formatter: &dyn ValueFormatter, all_curves: &[&(&'a BenchmarkId, Vec<f64>)], ) -> (&'static str, Vec<(Option<&'a String>, Vec<f64>, Vec<f64>)>)118 fn line_comparison_series_data<'a>(
119 formatter: &dyn ValueFormatter,
120 all_curves: &[&(&'a BenchmarkId, Vec<f64>)],
121 ) -> (&'static str, Vec<(Option<&'a String>, Vec<f64>, Vec<f64>)>) {
122 let max = all_curves
123 .iter()
124 .map(|&&(_, ref data)| Sample::new(data).mean())
125 .fold(::std::f64::NAN, f64::max);
126
127 let mut dummy = [1.0];
128 let unit = formatter.scale_values(max, &mut dummy);
129
130 let mut series_data = vec![];
131
132 // This assumes the curves are sorted. It also assumes that the benchmark IDs all have numeric
133 // values or throughputs and that value is sensible (ie. not a mix of bytes and elements
134 // or whatnot)
135 for (key, group) in &all_curves.iter().group_by(|&&&(ref id, _)| &id.function_id) {
136 let mut tuples: Vec<_> = group
137 .map(|&&(ref id, ref sample)| {
138 // Unwrap is fine here because it will only fail if the assumptions above are not true
139 // ie. programmer error.
140 let x = id.as_number().unwrap();
141 let y = Sample::new(sample).mean();
142
143 (x, y)
144 })
145 .collect();
146 tuples.sort_by(|&(ax, _), &(bx, _)| (ax.partial_cmp(&bx).unwrap_or(Ordering::Less)));
147 let function_name = key.as_ref();
148 let (xs, mut ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
149 formatter.scale_values(max, &mut ys);
150 series_data.push((function_name, xs, ys));
151 }
152 (unit, series_data)
153 }
154
violin( formatter: &dyn ValueFormatter, title: &str, all_curves: &[&(&BenchmarkId, Vec<f64>)], path: &Path, axis_scale: AxisScale, )155 pub fn violin(
156 formatter: &dyn ValueFormatter,
157 title: &str,
158 all_curves: &[&(&BenchmarkId, Vec<f64>)],
159 path: &Path,
160 axis_scale: AxisScale,
161 ) {
162 let all_curves_vec = all_curves.iter().rev().cloned().collect::<Vec<_>>();
163 let all_curves: &[&(&BenchmarkId, Vec<f64>)] = &*all_curves_vec;
164
165 let mut kdes = all_curves
166 .iter()
167 .map(|&&(ref id, ref sample)| {
168 let (x, mut y) = kde::sweep(Sample::new(sample), KDE_POINTS, None);
169 let y_max = Sample::new(&y).max();
170 for y in y.iter_mut() {
171 *y /= y_max;
172 }
173
174 (id.as_title(), x, y)
175 })
176 .collect::<Vec<_>>();
177
178 let mut xs = kdes
179 .iter()
180 .flat_map(|&(_, ref x, _)| x.iter())
181 .filter(|&&x| x > 0.);
182 let (mut min, mut max) = {
183 let &first = xs.next().unwrap();
184 (first, first)
185 };
186 for &e in xs {
187 if e < min {
188 min = e;
189 } else if e > max {
190 max = e;
191 }
192 }
193 let mut dummy = [1.0];
194 let unit = formatter.scale_values(max, &mut dummy);
195 kdes.iter_mut().for_each(|&mut (_, ref mut xs, _)| {
196 formatter.scale_values(max, xs);
197 });
198
199 let mut x_range =
200 plotters::data::fitting_range(kdes.iter().map(|(_, xs, _)| xs.iter()).flatten());
201 x_range.start = 0.0;
202 let y_range = -0.5..all_curves.len() as f64 - 0.5;
203
204 let size = (960, 150 + (18 * all_curves.len() as u32));
205
206 let root_area = SVGBackend::new(&path, size)
207 .into_drawing_area()
208 .titled(&format!("{}: Violin plot", title), (DEFAULT_FONT, 20))
209 .unwrap();
210
211 match axis_scale {
212 AxisScale::Linear => draw_violin_figure(root_area, unit, x_range, y_range, kdes),
213 AxisScale::Logarithmic => {
214 draw_violin_figure(root_area, unit, LogRange(x_range), y_range, kdes)
215 }
216 }
217 }
218
219 #[allow(clippy::type_complexity)]
draw_violin_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>( root_area: DrawingArea<SVGBackend, Shift>, unit: &'static str, x_range: XR, y_range: YR, data: Vec<(&str, Box<[f64]>, Box<[f64]>)>, ) where XR::CoordDescType: PlottersValueFormatter<f64>, YR::CoordDescType: PlottersValueFormatter<f64>,220 fn draw_violin_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
221 root_area: DrawingArea<SVGBackend, Shift>,
222 unit: &'static str,
223 x_range: XR,
224 y_range: YR,
225 data: Vec<(&str, Box<[f64]>, Box<[f64]>)>,
226 ) where
227 XR::CoordDescType: PlottersValueFormatter<f64>,
228 YR::CoordDescType: PlottersValueFormatter<f64>,
229 {
230 let mut chart = ChartBuilder::on(&root_area)
231 .margin((5).percent())
232 .set_label_area_size(LabelAreaPosition::Left, (10).percent_width().min(60))
233 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_width().min(40))
234 .build_cartesian_2d(x_range, y_range)
235 .unwrap();
236
237 chart
238 .configure_mesh()
239 .disable_mesh()
240 .y_desc("Input")
241 .x_desc(format!("Average time ({})", unit))
242 .y_label_style((DEFAULT_FONT, 10))
243 .y_label_formatter(&|v: &f64| data[v.round() as usize].0.to_string())
244 .y_labels(data.len())
245 .draw()
246 .unwrap();
247
248 for (i, (_, x, y)) in data.into_iter().enumerate() {
249 let base = i as f64;
250
251 chart
252 .draw_series(AreaSeries::new(
253 x.iter().zip(y.iter()).map(|(x, y)| (*x, base + *y / 2.0)),
254 base,
255 &DARK_BLUE,
256 ))
257 .unwrap();
258
259 chart
260 .draw_series(AreaSeries::new(
261 x.iter().zip(y.iter()).map(|(x, y)| (*x, base - *y / 2.0)),
262 base,
263 &DARK_BLUE,
264 ))
265 .unwrap();
266 }
267 }
268