• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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().flat_map(|(_, xs, _)| xs.iter()));
35     let y_range =
36         plotters::data::fitting_range(series_data.iter().flat_map(|(_, _, ys)| ys.iter()));
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             x_range.log_scale(),
50             y_range.log_scale(),
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             series.label(name).legend(move |(x, y)| {
101                 Rectangle::new(
102                     [(x, y - 5), (x + 20, y + 5)],
103                     COMPARISON_COLORS[id % NUM_COLORS].filled(),
104                 )
105             });
106         }
107     }
108 
109     chart
110         .configure_series_labels()
111         .position(SeriesLabelPosition::UpperLeft)
112         .draw()
113         .unwrap();
114 }
115 
116 #[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>)>)117 fn line_comparison_series_data<'a>(
118     formatter: &dyn ValueFormatter,
119     all_curves: &[&(&'a BenchmarkId, Vec<f64>)],
120 ) -> (&'static str, Vec<(Option<&'a String>, Vec<f64>, Vec<f64>)>) {
121     let max = all_curves
122         .iter()
123         .map(|&&(_, ref data)| Sample::new(data).mean())
124         .fold(::std::f64::NAN, f64::max);
125 
126     let mut dummy = [1.0];
127     let unit = formatter.scale_values(max, &mut dummy);
128 
129     let mut series_data = vec![];
130 
131     // This assumes the curves are sorted. It also assumes that the benchmark IDs all have numeric
132     // values or throughputs and that value is sensible (ie. not a mix of bytes and elements
133     // or whatnot)
134     for (key, group) in &all_curves.iter().group_by(|&&&(id, _)| &id.function_id) {
135         let mut tuples: Vec<_> = group
136             .map(|&&(id, ref sample)| {
137                 // Unwrap is fine here because it will only fail if the assumptions above are not true
138                 // ie. programmer error.
139                 let x = id.as_number().unwrap();
140                 let y = Sample::new(sample).mean();
141 
142                 (x, y)
143             })
144             .collect();
145         tuples.sort_by(|&(ax, _), &(bx, _)| (ax.partial_cmp(&bx).unwrap_or(Ordering::Less)));
146         let function_name = key.as_ref();
147         let (xs, mut ys): (Vec<_>, Vec<_>) = tuples.into_iter().unzip();
148         formatter.scale_values(max, &mut ys);
149         series_data.push((function_name, xs, ys));
150     }
151     (unit, series_data)
152 }
153 
violin( formatter: &dyn ValueFormatter, title: &str, all_curves: &[&(&BenchmarkId, Vec<f64>)], path: &Path, axis_scale: AxisScale, )154 pub fn violin(
155     formatter: &dyn ValueFormatter,
156     title: &str,
157     all_curves: &[&(&BenchmarkId, Vec<f64>)],
158     path: &Path,
159     axis_scale: AxisScale,
160 ) {
161     let all_curves_vec = all_curves.iter().rev().cloned().collect::<Vec<_>>();
162     let all_curves: &[&(&BenchmarkId, Vec<f64>)] = &*all_curves_vec;
163 
164     let mut kdes = all_curves
165         .iter()
166         .map(|&&(id, ref sample)| {
167             let (x, mut y) = kde::sweep(Sample::new(sample), KDE_POINTS, None);
168             let y_max = Sample::new(&y).max();
169             for y in y.iter_mut() {
170                 *y /= y_max;
171             }
172 
173             (id.as_title(), x, y)
174         })
175         .collect::<Vec<_>>();
176 
177     let mut xs = kdes
178         .iter()
179         .flat_map(|&(_, ref x, _)| x.iter())
180         .filter(|&&x| x > 0.);
181     let (mut min, mut max) = {
182         let &first = xs.next().unwrap();
183         (first, first)
184     };
185     for &e in xs {
186         if e < min {
187             min = e;
188         } else if e > max {
189             max = e;
190         }
191     }
192     let mut dummy = [1.0];
193     let unit = formatter.scale_values(max, &mut dummy);
194     kdes.iter_mut().for_each(|&mut (_, ref mut xs, _)| {
195         formatter.scale_values(max, xs);
196     });
197 
198     let mut x_range = plotters::data::fitting_range(kdes.iter().flat_map(|(_, xs, _)| xs.iter()));
199     x_range.start = 0.0;
200     let y_range = -0.5..all_curves.len() as f64 - 0.5;
201 
202     let size = (960, 150 + (18 * all_curves.len() as u32));
203 
204     let root_area = SVGBackend::new(&path, size)
205         .into_drawing_area()
206         .titled(&format!("{}: Violin plot", title), (DEFAULT_FONT, 20))
207         .unwrap();
208 
209     match axis_scale {
210         AxisScale::Linear => draw_violin_figure(root_area, unit, x_range, y_range, kdes),
211         AxisScale::Logarithmic => {
212             draw_violin_figure(root_area, unit, x_range.log_scale(), y_range, kdes)
213         }
214     }
215 }
216 
217 #[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>,218 fn draw_violin_figure<XR: AsRangedCoord<Value = f64>, YR: AsRangedCoord<Value = f64>>(
219     root_area: DrawingArea<SVGBackend, Shift>,
220     unit: &'static str,
221     x_range: XR,
222     y_range: YR,
223     data: Vec<(&str, Box<[f64]>, Box<[f64]>)>,
224 ) where
225     XR::CoordDescType: PlottersValueFormatter<f64>,
226     YR::CoordDescType: PlottersValueFormatter<f64>,
227 {
228     let mut chart = ChartBuilder::on(&root_area)
229         .margin((5).percent())
230         .set_label_area_size(LabelAreaPosition::Left, (10).percent_width().min(60))
231         .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_width().min(40))
232         .build_cartesian_2d(x_range, y_range)
233         .unwrap();
234 
235     chart
236         .configure_mesh()
237         .disable_mesh()
238         .y_desc("Input")
239         .x_desc(format!("Average time ({})", unit))
240         .y_label_style((DEFAULT_FONT, 10))
241         .y_label_formatter(&|v: &f64| data[v.round() as usize].0.to_string())
242         .y_labels(data.len())
243         .draw()
244         .unwrap();
245 
246     for (i, (_, x, y)) in data.into_iter().enumerate() {
247         let base = i as f64;
248 
249         chart
250             .draw_series(AreaSeries::new(
251                 x.iter().zip(y.iter()).map(|(x, y)| (*x, base + *y / 2.0)),
252                 base,
253                 &DARK_BLUE,
254             ))
255             .unwrap();
256 
257         chart
258             .draw_series(AreaSeries::new(
259                 x.iter().zip(y.iter()).map(|(x, y)| (*x, base - *y / 2.0)),
260                 base,
261                 &DARK_BLUE,
262             ))
263             .unwrap();
264     }
265 }
266