1 | use super::*; |
---|---|

2 | use crate::kde; |

3 | use crate::measurement::ValueFormatter; |

4 | use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext}; |

5 | use std::process::Child; |

6 | |

7 | pub(crate) fn pdf( |

8 | id: &BenchmarkId, |

9 | context: &ReportContext, |

10 | formatter: &dyn ValueFormatter, |

11 | measurements: &MeasurementData<'_>, |

12 | size: Option<Size>, |

13 | ) -> Child { |

14 | let avg_times = &measurements.avg_times; |

15 | let typical = avg_times.max(); |

16 | let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect(); |

17 | let unit = formatter.scale_values(typical, &mut scaled_avg_times); |

18 | let scaled_avg_times = Sample::new(&scaled_avg_times); |

19 | |

20 | let mean = scaled_avg_times.mean(); |

21 | |

22 | let iter_counts = measurements.iter_counts(); |

23 | let &max_iters = iter_counts |

24 | .iter() |

25 | .max_by_key(|&&iters| iters as u64) |

26 | .unwrap(); |

27 | let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; |

28 | let y_scale = 10f64.powi(-exponent); |

29 | |

30 | let y_label = if exponent == 0 { |

31 | "Iterations".to_owned() |

32 | } else { |

33 | format!("Iterations (x 10^{})", exponent) |

34 | }; |

35 | |

36 | let (xs, ys) = kde::sweep(scaled_avg_times, KDE_POINTS, None); |

37 | let (lost, lomt, himt, hist) = avg_times.fences(); |

38 | let mut fences = [lost, lomt, himt, hist]; |

39 | let _ = formatter.scale_values(typical, &mut fences); |

40 | let [lost, lomt, himt, hist] = fences; |

41 | |

42 | let vertical = &[0., max_iters]; |

43 | let zeros = iter::repeat(0); |

44 | |

45 | let mut figure = Figure::new(); |

46 | figure |

47 | .set(Font(DEFAULT_FONT)) |

48 | .set(size.unwrap_or(SIZE)) |

49 | .configure(Axis::BottomX, |a| { |

50 | let xs_ = Sample::new(&xs); |

51 | a.set(Label(format!("Average time ({})", unit))) |

52 | .set(Range::Limits(xs_.min(), xs_.max())) |

53 | }) |

54 | .configure(Axis::LeftY, |a| { |

55 | a.set(Label(y_label)) |

56 | .set(Range::Limits(0., max_iters * y_scale)) |

57 | .set(ScaleFactor(y_scale)) |

58 | }) |

59 | .configure(Axis::RightY, |a| a.set(Label("Density (a.u.)"))) |

60 | .configure(Key, |k| { |

61 | k.set(Justification::Left) |

62 | .set(Order::SampleText) |

63 | .set(Position::Outside(Vertical::Top, Horizontal::Right)) |

64 | }) |

65 | .plot( |

66 | FilledCurve { |

67 | x: &*xs, |

68 | y1: &*ys, |

69 | y2: zeros, |

70 | }, |

71 | |c| { |

72 | c.set(Axes::BottomXRightY) |

73 | .set(DARK_BLUE) |

74 | .set(Label("PDF")) |

75 | .set(Opacity(0.25)) |

76 | }, |

77 | ) |

78 | .plot( |

79 | Lines { |

80 | x: &[mean, mean], |

81 | y: vertical, |

82 | }, |

83 | |c| { |

84 | c.set(DARK_BLUE) |

85 | .set(LINEWIDTH) |

86 | .set(LineType::Dash) |

87 | .set(Label("Mean")) |

88 | }, |

89 | ) |

90 | .plot( |

91 | Points { |

92 | x: avg_times |

93 | .iter() |

94 | .zip(scaled_avg_times.iter()) |

95 | .filter_map( |

96 | |((_, label), t)| { |

97 | if label.is_outlier() { |

98 | None |

99 | } else { |

100 | Some(t) |

101 | } |

102 | }, |

103 | ), |

104 | y: avg_times |

105 | .iter() |

106 | .zip(iter_counts.iter()) |

107 | .filter_map( |

108 | |((_, label), i)| { |

109 | if label.is_outlier() { |

110 | None |

111 | } else { |

112 | Some(i) |

113 | } |

114 | }, |

115 | ), |

116 | }, |

117 | |c| { |

118 | c.set(DARK_BLUE) |

119 | .set(Label(" \"Clean \"sample")) |

120 | .set(PointType::FilledCircle) |

121 | .set(POINT_SIZE) |

122 | }, |

123 | ) |

124 | .plot( |

125 | Points { |

126 | x: avg_times |

127 | .iter() |

128 | .zip(scaled_avg_times.iter()) |

129 | .filter_map( |

130 | |((_, label), t)| { |

131 | if label.is_mild() { |

132 | Some(t) |

133 | } else { |

134 | None |

135 | } |

136 | }, |

137 | ), |

138 | y: avg_times |

139 | .iter() |

140 | .zip(iter_counts.iter()) |

141 | .filter_map( |

142 | |((_, label), i)| { |

143 | if label.is_mild() { |

144 | Some(i) |

145 | } else { |

146 | None |

147 | } |

148 | }, |

149 | ), |

150 | }, |

151 | |c| { |

152 | c.set(DARK_ORANGE) |

153 | .set(Label("Mild outliers")) |

154 | .set(POINT_SIZE) |

155 | .set(PointType::FilledCircle) |

156 | }, |

157 | ) |

158 | .plot( |

159 | Points { |

160 | x: avg_times |

161 | .iter() |

162 | .zip(scaled_avg_times.iter()) |

163 | .filter_map( |

164 | |((_, label), t)| { |

165 | if label.is_severe() { |

166 | Some(t) |

167 | } else { |

168 | None |

169 | } |

170 | }, |

171 | ), |

172 | y: avg_times |

173 | .iter() |

174 | .zip(iter_counts.iter()) |

175 | .filter_map( |

176 | |((_, label), i)| { |

177 | if label.is_severe() { |

178 | Some(i) |

179 | } else { |

180 | None |

181 | } |

182 | }, |

183 | ), |

184 | }, |

185 | |c| { |

186 | c.set(DARK_RED) |

187 | .set(Label("Severe outliers")) |

188 | .set(POINT_SIZE) |

189 | .set(PointType::FilledCircle) |

190 | }, |

191 | ) |

192 | .plot( |

193 | Lines { |

194 | x: &[lomt, lomt], |

195 | y: vertical, |

196 | }, |

197 | |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash), |

198 | ) |

199 | .plot( |

200 | Lines { |

201 | x: &[himt, himt], |

202 | y: vertical, |

203 | }, |

204 | |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash), |

205 | ) |

206 | .plot( |

207 | Lines { |

208 | x: &[lost, lost], |

209 | y: vertical, |

210 | }, |

211 | |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash), |

212 | ) |

213 | .plot( |

214 | Lines { |

215 | x: &[hist, hist], |

216 | y: vertical, |

217 | }, |

218 | |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash), |

219 | ); |

220 | figure.set(Title(gnuplot_escape(id.as_title()))); |

221 | |

222 | let path = context.report_path(id, "pdf.svg"); |

223 | debug_script(&path, &figure); |

224 | figure.set(Output(path)).draw().unwrap() |

225 | } |

226 | |

227 | pub(crate) fn pdf_small( |

228 | id: &BenchmarkId, |

229 | context: &ReportContext, |

230 | formatter: &dyn ValueFormatter, |

231 | measurements: &MeasurementData<'_>, |

232 | size: Option<Size>, |

233 | ) -> Child { |

234 | let avg_times = &*measurements.avg_times; |

235 | let typical = avg_times.max(); |

236 | let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect(); |

237 | let unit = formatter.scale_values(typical, &mut scaled_avg_times); |

238 | let scaled_avg_times = Sample::new(&scaled_avg_times); |

239 | let mean = scaled_avg_times.mean(); |

240 | |

241 | let (xs, ys, mean_y) = kde::sweep_and_estimate(scaled_avg_times, KDE_POINTS, None, mean); |

242 | let xs_ = Sample::new(&xs); |

243 | let ys_ = Sample::new(&ys); |

244 | |

245 | let y_limit = ys_.max() * 1.1; |

246 | let zeros = iter::repeat(0); |

247 | |

248 | let mut figure = Figure::new(); |

249 | figure |

250 | .set(Font(DEFAULT_FONT)) |

251 | .set(size.unwrap_or(SIZE)) |

252 | .configure(Axis::BottomX, |a| { |

253 | a.set(Label(format!("Average time ({})", unit))) |

254 | .set(Range::Limits(xs_.min(), xs_.max())) |

255 | }) |

256 | .configure(Axis::LeftY, |a| { |

257 | a.set(Label("Density (a.u.)")) |

258 | .set(Range::Limits(0., y_limit)) |

259 | }) |

260 | .configure(Axis::RightY, |a| a.hide()) |

261 | .configure(Key, |k| k.hide()) |

262 | .plot( |

263 | FilledCurve { |

264 | x: &*xs, |

265 | y1: &*ys, |

266 | y2: zeros, |

267 | }, |

268 | |c| { |

269 | c.set(Axes::BottomXRightY) |

270 | .set(DARK_BLUE) |

271 | .set(Label("PDF")) |

272 | .set(Opacity(0.25)) |

273 | }, |

274 | ) |

275 | .plot( |

276 | Lines { |

277 | x: &[mean, mean], |

278 | y: &[0., mean_y], |

279 | }, |

280 | |c| c.set(DARK_BLUE).set(LINEWIDTH).set(Label("Mean")), |

281 | ); |

282 | |

283 | let path = context.report_path(id, "pdf_small.svg"); |

284 | debug_script(&path, &figure); |

285 | figure.set(Output(path)).draw().unwrap() |

286 | } |

287 | |

288 | fn pdf_comparison_figure( |

289 | formatter: &dyn ValueFormatter, |

290 | measurements: &MeasurementData<'_>, |

291 | comparison: &ComparisonData, |

292 | size: Option<Size>, |

293 | ) -> Figure { |

294 | let base_avg_times = Sample::new(&comparison.base_avg_times); |

295 | let typical = base_avg_times.max().max(measurements.avg_times.max()); |

296 | let mut scaled_base_avg_times: Vec<f64> = comparison.base_avg_times.clone(); |

297 | let unit = formatter.scale_values(typical, &mut scaled_base_avg_times); |

298 | let scaled_base_avg_times = Sample::new(&scaled_base_avg_times); |

299 | |

300 | let mut scaled_new_avg_times: Vec<f64> = (&measurements.avg_times as &Sample<f64>) |

301 | .iter() |

302 | .cloned() |

303 | .collect(); |

304 | let _ = formatter.scale_values(typical, &mut scaled_new_avg_times); |

305 | let scaled_new_avg_times = Sample::new(&scaled_new_avg_times); |

306 | |

307 | let base_mean = scaled_base_avg_times.mean(); |

308 | let new_mean = scaled_new_avg_times.mean(); |

309 | |

310 | let (base_xs, base_ys, base_y_mean) = |

311 | kde::sweep_and_estimate(scaled_base_avg_times, KDE_POINTS, None, base_mean); |

312 | let (xs, ys, y_mean) = |

313 | kde::sweep_and_estimate(scaled_new_avg_times, KDE_POINTS, None, new_mean); |

314 | |

315 | let zeros = iter::repeat(0); |

316 | |

317 | let mut figure = Figure::new(); |

318 | figure |

319 | .set(Font(DEFAULT_FONT)) |

320 | .set(size.unwrap_or(SIZE)) |

321 | .configure(Axis::BottomX, |a| { |

322 | a.set(Label(format!("Average time ({})", unit))) |

323 | }) |

324 | .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)"))) |

325 | .configure(Axis::RightY, |a| a.hide()) |

326 | .configure(Key, |k| { |

327 | k.set(Justification::Left) |

328 | .set(Order::SampleText) |

329 | .set(Position::Outside(Vertical::Top, Horizontal::Right)) |

330 | }) |

331 | .plot( |

332 | FilledCurve { |

333 | x: &*base_xs, |

334 | y1: &*base_ys, |

335 | y2: zeros.clone(), |

336 | }, |

337 | |c| c.set(DARK_RED).set(Label("Base PDF")).set(Opacity( 0.5)), |

338 | ) |

339 | .plot( |

340 | Lines { |

341 | x: &[base_mean, base_mean], |

342 | y: &[0., base_y_mean], |

343 | }, |

344 | |c| c.set(DARK_RED).set(Label("Base Mean")).set(LINEWIDTH), |

345 | ) |

346 | .plot( |

347 | FilledCurve { |

348 | x: &*xs, |

349 | y1: &*ys, |

350 | y2: zeros, |

351 | }, |

352 | |c| c.set(DARK_BLUE).set(Label("New PDF")).set(Opacity( 0.5)), |

353 | ) |

354 | .plot( |

355 | Lines { |

356 | x: &[new_mean, new_mean], |

357 | y: &[0., y_mean], |

358 | }, |

359 | |c| c.set(DARK_BLUE).set(Label("New Mean")).set(LINEWIDTH), |

360 | ); |

361 | figure |

362 | } |

363 | |

364 | pub(crate) fn pdf_comparison( |

365 | id: &BenchmarkId, |

366 | context: &ReportContext, |

367 | formatter: &dyn ValueFormatter, |

368 | measurements: &MeasurementData<'_>, |

369 | comparison: &ComparisonData, |

370 | size: Option<Size>, |

371 | ) -> Child { |

372 | let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size); |

373 | figure.set(Title(gnuplot_escape(id.as_title()))); |

374 | let path = context.report_path(id, "both/pdf.svg"); |

375 | debug_script(&path, &figure); |

376 | figure.set(Output(path)).draw().unwrap() |

377 | } |

378 | |

379 | pub(crate) fn pdf_comparison_small( |

380 | id: &BenchmarkId, |

381 | context: &ReportContext, |

382 | formatter: &dyn ValueFormatter, |

383 | measurements: &MeasurementData<'_>, |

384 | comparison: &ComparisonData, |

385 | size: Option<Size>, |

386 | ) -> Child { |

387 | let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size); |

388 | figure.configure(Key, |k| k.hide()); |

389 | let path = context.report_path(id, "relative_pdf_small.svg"); |

390 | debug_script(&path, &figure); |

391 | figure.set(Output(path)).draw().unwrap() |

392 | } |

393 |