1use std::{
7 fs::{File, OpenOptions},
8 io::Write,
9};
10
11use gnuplot::{
12 AutoOption, AxesCommon, ColorType,
13 Coordinate::Graph,
14 DashType, Figure,
15 LabelOption::{Rotate, TextOffset},
16 MarginSide::{MarginLeft, MarginTop},
17 PaletteType,
18 PlotOption::{Caption, Color, LineStyle, PointSymbol},
19 Tick,
20 TickOption::{Inward, Mirror},
21};
22
23use crate::command_line::ScalingParams;
24
25use super::raw::{
26 correlation, TalliedData, TalliesReport, TimerReport, TimerSV, N_TIMERS, TIMERS_ARR,
27};
28
29pub struct ComparisonResults {
35 pub old: TimerReport,
37 pub new: TimerReport,
39 pub percents: [f64; N_TIMERS],
41}
42
43impl ComparisonResults {
44 pub fn save(&self) {
46 let mut file = OpenOptions::new()
47 .write(true)
48 .create(true)
49 .truncate(true)
50 .open("comparison.csv")
51 .unwrap();
52 writeln!(file, "section,old,new,change").unwrap();
53 TIMERS_ARR.iter().for_each(|section| {
54 writeln!(
55 file,
56 "{},{},{},{}",
57 *section,
58 self.old[*section].total,
59 self.new[*section].total,
60 self.percents[*section as usize]
61 )
62 .unwrap();
63 });
64 }
65
66 pub fn plot(&self) {
68 let mut fg = Figure::new();
70 fg.set_terminal("pngcairo size 800, 800", "comparison.png")
71 .set_title("Timers Reports Comparison");
72 let x_coords: [usize; N_TIMERS] = core::array::from_fn(|i| i);
74 let x_tics = TIMERS_ARR.map(|section| {
75 Tick::Major(
76 section as usize,
77 AutoOption::Fix(match section {
78 TimerSV::Main => "Main Program",
79 TimerSV::PopulationControl => "Population Control",
80 TimerSV::CycleTracking => "Tracking Phase",
81 TimerSV::CycleTrackingProcess => "Process Phase",
82 TimerSV::CycleTrackingSort => "Sorting Phase",
83 TimerSV::CycleSync => "Sync Phase",
84 }),
85 )
86 });
87 let width = 0.25;
88
89 let old_y: [f64; N_TIMERS] = TIMERS_ARR.map(|t| self.old[t].total / 1.0e6);
90 let new_y: [f64; N_TIMERS] = TIMERS_ARR.map(|t| self.new[t].total / 1.0e6);
91 let new_color = if self.percents[0] > 0.0 {
92 Color(ColorType::RGBString("#FF0000")) } else {
94 Color(ColorType::RGBString("#00BB00")) };
96
97 fg.axes2d()
99 .set_x_ticks_custom(x_tics, &[Inward(false), Mirror(false)], &[Rotate(-45.0)])
100 .set_x_label("Section", &[TextOffset(0.0, 1.5)])
101 .set_y_grid(true)
102 .set_y_minor_grid(true)
103 .set_y_label("Total Time Spent in Section (s)", &[])
104 .set_y_log(Some(10.0))
105 .boxes(
106 x_coords.iter().map(|x| *x as f64 - width / 2.0),
107 old_y,
108 &[Caption("Old times"), Color(ColorType::RGBString("#000077"))],
109 )
110 .boxes(
111 x_coords.iter().map(|x| *x as f64 + width / 2.0),
112 new_y,
113 &[Caption("New times"), new_color],
114 )
115 .set_box_width(width, false);
116
117 fg.show().unwrap();
118 }
119}
120
121impl From<(TimerReport, TimerReport)> for ComparisonResults {
123 fn from((old, new): (TimerReport, TimerReport)) -> Self {
124 let relative_change =
125 |section: TimerSV| (new[section].mean - old[section].mean) / old[section].mean;
126
127 let percents = [
128 TimerSV::Main,
129 TimerSV::PopulationControl,
130 TimerSV::CycleTracking,
131 TimerSV::CycleTrackingProcess,
132 TimerSV::CycleTrackingSort,
133 TimerSV::CycleSync,
134 ]
135 .map(|section| relative_change(section) * 100.0);
136
137 Self { old, new, percents }
138 }
139}
140
141pub const CORRELATION_COLS: [TalliedData; 11] = [
147 TalliedData::Start,
148 TalliedData::Rr,
149 TalliedData::Split,
150 TalliedData::Absorb,
151 TalliedData::Scatter,
152 TalliedData::Fission,
153 TalliedData::Produce,
154 TalliedData::Collision,
155 TalliedData::Escape,
156 TalliedData::Census,
157 TalliedData::NumSeg,
158];
159
160pub const CORRELATION_ROWS: [TalliedData; 4] = [
162 TalliedData::CycleSync,
163 TalliedData::CycleTracking,
164 TalliedData::PopulationControl,
165 TalliedData::Cycle,
166];
167
168pub struct CorrelationResults {
170 pub corr_data: [f64; 11 * 4],
172}
173
174impl CorrelationResults {
175 pub fn save(&self) {
177 let mut file = OpenOptions::new()
178 .write(true)
179 .create(true)
180 .truncate(true)
181 .open("correlation.csv")
182 .unwrap();
183 writeln!(
184 file,
185 ",Start,Rr,Split,Absorb,Scatter,Fission,Produce,Collision,Escape,Census,NumSeg"
186 )
187 .unwrap();
188 (0..4).for_each(|idx: usize| {
189 writeln!(
190 file,
191 "{},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}",
192 match idx {
193 0 => "CycleSync",
194 1 => "CycleTracking",
195 2 => "PopulationControl",
196 3 => "Cycle",
197 _ => unreachable!(),
198 },
199 self.corr_data[11 * idx],
200 self.corr_data[1 + 11 * idx],
201 self.corr_data[2 + 11 * idx],
202 self.corr_data[3 + 11 * idx],
203 self.corr_data[4 + 11 * idx],
204 self.corr_data[5 + 11 * idx],
205 self.corr_data[6 + 11 * idx],
206 self.corr_data[7 + 11 * idx],
207 self.corr_data[8 + 11 * idx],
208 self.corr_data[9 + 11 * idx],
209 self.corr_data[10 + 11 * idx],
210 )
211 .unwrap();
212 });
213 }
214
215 pub fn plot(&self) {
217 let mut fg = Figure::new();
219 fg.set_terminal("pngcairo size 1300, 600", "correlation.png")
220 .set_title("Event Correlation Matrix");
221 let n_col = CORRELATION_COLS.len();
223 let n_row = CORRELATION_ROWS.len();
224 let x: [usize; CORRELATION_COLS.len()] = core::array::from_fn(|i| i);
225 let y: [usize; CORRELATION_ROWS.len()] = core::array::from_fn(|i| i);
226 let x_tics = x.map(|event_idx| {
227 Tick::Major(
228 event_idx as f64 - 0.5,
229 AutoOption::Fix(CORRELATION_COLS[event_idx].to_string()),
230 )
231 });
232 let y_tics = y.map(|event_idx| {
233 Tick::Major(
234 event_idx as f64 - 0.5,
235 AutoOption::Fix(CORRELATION_ROWS[event_idx].to_string()),
236 )
237 });
238
239 fg.axes2d()
241 .set_aspect_ratio(AutoOption::Fix(0.35))
242 .set_margins(&[MarginLeft(0.10)])
243 .set_x_ticks_custom(
244 x_tics,
245 &[Inward(false), Mirror(false)],
246 &[Rotate(-45.0), TextOffset(4.0, -1.0)],
247 )
248 .set_y_ticks_custom(
249 y_tics,
250 &[Inward(false), Mirror(false)],
251 &[Rotate(45.0), TextOffset(0.0, 2.0)],
252 )
253 .set_cb_grid(true)
254 .set_grid_options(true, &[LineStyle(DashType::Solid)])
255 .set_x_grid(true)
256 .set_y_grid(true)
257 .set_palette(PaletteType::Custom(&[
258 (-5.0, 0.0, 0.0, 1.0),
259 (0.0, 1.0, 1.0, 1.0),
260 (5.0, 1.0, 0.0, 0.0),
261 ]))
262 .image(self.corr_data, n_row, n_col, None, &[]);
263
264 fg.show().unwrap();
265 }
266}
267
268impl From<TalliesReport> for CorrelationResults {
270 fn from(report: TalliesReport) -> Self {
271 let table: [[f64; 11]; 4] = CORRELATION_ROWS.map(|tallied_data| {
273 CORRELATION_COLS
274 .map(|tallied_event| correlation(&report[tallied_data], &report[tallied_event]))
275 });
276
277 let flat_table: &[f64; 11 * 4] = unsafe { std::mem::transmute(&table) };
279
280 Self {
281 corr_data: *flat_table,
282 }
283 }
284}
285
286pub enum ScalingType {
292 Weak,
294 Strong(usize),
296}
297
298pub struct ScalingResults {
300 pub n_threads: Vec<usize>,
302 pub total_exec_times: Vec<f64>,
304 pub population_control_avgs: Vec<f64>,
306 pub tracking_avgs: Vec<f64>,
308 pub tracking_process_avgs: Vec<f64>,
310 pub tracking_sort_avgs: Vec<f64>,
312 pub sync_avgs: Vec<f64>,
314 pub scaling_type: ScalingType,
316}
317
318impl ScalingResults {
319 pub fn save_tracking(&self) {
321 let mut file = OpenOptions::new()
322 .write(true)
323 .create(true)
324 .truncate(true)
325 .open(match self.scaling_type {
326 ScalingType::Weak => "weak_scaling_tracking.csv",
327 ScalingType::Strong(_) => "strong_scaling_tracking.csv",
328 })
329 .unwrap();
330 writeln!(
331 file,
332 "n_threads,TrackingAvg,TrackingAvgIdeal,TrackingProcessAvg,TrackingSortAvg"
333 )
334 .unwrap();
335 let avg_ref = self.tracking_avgs[0];
336 let n_elem = self.n_threads.len();
337 assert_eq!(self.tracking_avgs.len(), n_elem);
338 assert_eq!(self.tracking_process_avgs.len(), n_elem);
339 assert_eq!(self.tracking_sort_avgs.len(), n_elem);
340 for idx in 0..n_elem {
341 let ideal = match self.scaling_type {
342 ScalingType::Weak => avg_ref,
343 ScalingType::Strong(factor) => avg_ref / (factor.pow(idx as u32) as f64),
344 };
345 writeln!(
346 file,
347 "{},{},{},{},{}",
348 self.n_threads[idx],
349 self.tracking_avgs[idx],
350 ideal,
351 self.tracking_process_avgs[idx],
352 self.tracking_sort_avgs[idx]
353 )
354 .unwrap();
355 }
356 }
357
358 pub fn save_others(&self) {
360 let mut file = OpenOptions::new()
361 .write(true)
362 .create(true)
363 .truncate(true)
364 .open(match self.scaling_type {
365 ScalingType::Weak => "weak_scaling_others.csv",
366 ScalingType::Strong(_) => "strong_scaling_others.csv",
367 })
368 .unwrap();
369 writeln!(file, "n_threads,TotalExecTime,PopulationControlAvg,SyncAvg").unwrap();
370 let n_elem = self.n_threads.len();
371 assert_eq!(self.total_exec_times.len(), n_elem);
372 assert_eq!(self.population_control_avgs.len(), n_elem);
373 assert_eq!(self.sync_avgs.len(), n_elem);
374 for idx in 0..n_elem {
375 writeln!(
376 file,
377 "{},{},{},{}",
378 self.n_threads[idx],
379 self.total_exec_times[idx],
380 self.population_control_avgs[idx],
381 self.sync_avgs[idx]
382 )
383 .unwrap();
384 }
385 }
386
387 pub fn plot_se(&self) {
389 let mut fg = Figure::new();
390 let (out, title) = match self.scaling_type {
391 ScalingType::Weak => ("efficiency.png", "Ideal vs Real Efficiency"),
392 ScalingType::Strong(_) => ("speedup.png", "Ideal vs Real Speedup"),
393 };
394 fg.set_terminal("pngcairo", out).set_title(title);
395
396 let y_ideal: Vec<f64> = self
397 .n_threads
398 .iter()
399 .map(|n_thread| match self.scaling_type {
400 ScalingType::Weak => 1.0,
401 ScalingType::Strong(_) => *n_thread as f64,
402 })
403 .collect();
404
405 let y_real: Vec<f64> = self
406 .n_threads
407 .iter()
408 .enumerate()
409 .map(|(idx, _)| self.tracking_avgs[0] / self.tracking_avgs[idx])
410 .collect();
411
412 fg.axes2d()
413 .set_x_log(Some(self.n_threads[1] as f64 / self.n_threads[0] as f64))
414 .set_x_label("Number of Threads Used for Execution", &[])
415 .set_x_ticks(Some((AutoOption::Auto, 0)), &[Inward(false)], &[])
416 .set_y_log(match self.scaling_type {
417 ScalingType::Weak => None,
418 ScalingType::Strong(factor) => Some(factor as f64),
419 })
420 .set_y_label(
421 match self.scaling_type {
422 ScalingType::Weak => "Efficiency",
423 ScalingType::Strong(_) => "Speedup",
424 },
425 &[],
426 )
427 .set_y_grid(true)
428 .lines_points(
429 &self.n_threads,
430 &y_ideal,
431 &[
432 Caption("Ideal"),
433 Color(ColorType::RGBString("#00FF00")),
434 PointSymbol('x'),
435 ],
436 )
437 .lines_points(
438 &self.n_threads,
439 &y_real,
440 &[
441 Caption("Real"),
442 Color(ColorType::RGBString("#008800")),
443 PointSymbol('x'),
444 ],
445 );
446
447 fg.show().unwrap();
448 }
449
450 pub fn plot_tracking(&self) {
452 let mut fg = Figure::new();
454 let (out, title) = match self.scaling_type {
455 ScalingType::Weak => ("weak_scaling_tracking.png", "Weak Scaling Characteristics"),
456 ScalingType::Strong(_) => (
457 "strong_scaling_tracking.png",
458 "Strong Scaling Characteristics",
459 ),
460 };
461 fg.set_terminal("pngcairo", out).set_title(title);
462 let y_ideal: Vec<f64> = self
465 .n_threads
466 .iter()
467 .enumerate()
468 .map(|(idx, _)| match self.scaling_type {
469 ScalingType::Weak => self.tracking_avgs[0],
470 ScalingType::Strong(factor) => {
471 self.tracking_avgs[0] / (factor.pow(idx as u32) as f64)
472 }
473 })
474 .collect();
475
476 fg.axes2d()
478 .set_x_log(Some(self.n_threads[1] as f64 / self.n_threads[0] as f64))
479 .set_x_label("Number of Threads Used for Execution", &[])
480 .set_x_ticks(Some((AutoOption::Auto, 0)), &[Inward(false)], &[])
481 .set_y_log(match self.scaling_type {
482 ScalingType::Weak => None,
483 ScalingType::Strong(_) => Some(self.n_threads[1] as f64 / self.n_threads[0] as f64),
484 })
485 .set_y_label("Time (µs)", &[])
486 .set_y_grid(true)
487 .lines_points(
488 &self.n_threads,
489 &y_ideal,
490 &[
491 Caption("Ideal Average Tracking time"),
492 Color(ColorType::RGBString("#00FF00")),
493 PointSymbol('x'),
494 ],
495 )
496 .lines_points(
497 &self.n_threads,
498 &self.tracking_avgs,
499 &[
500 Caption("Average Tracking time"),
501 Color(ColorType::RGBString("#008800")),
502 PointSymbol('x'),
503 ],
504 )
505 .lines_points(
506 &self.n_threads,
507 &self.tracking_process_avgs,
508 &[
509 Caption("Average Processing time"),
510 Color(ColorType::RGBString("#CCCC00")),
511 PointSymbol('x'),
512 ],
513 )
514 .lines_points(
515 &self.n_threads,
516 &self.tracking_sort_avgs,
517 &[
518 Caption("Average Sorting time"),
519 Color(ColorType::RGBString("#999900")),
520 PointSymbol('x'),
521 ],
522 );
523
524 fg.show().unwrap();
525 }
526
527 pub fn plot_others(&self) {
529 let mut fg = Figure::new();
531 let (out, title) = match self.scaling_type {
532 ScalingType::Weak => ("weak_scaling_others.png", "Weak Scaling Characteristics"),
533 ScalingType::Strong(_) => (
534 "strong_scaling_others.png",
535 "Strong Scaling Characteristics",
536 ),
537 };
538 fg.set_terminal("pngcairo", out).set_title(title);
539
540 fg.axes2d()
542 .set_x_log(Some(self.n_threads[1] as f64 / self.n_threads[0] as f64))
543 .set_x_label("Number of Threads Used for Execution", &[])
544 .set_x_ticks(
545 Some((AutoOption::Auto, 0)),
546 &[Inward(false), Mirror(false)],
547 &[],
548 )
549 .set_y_range(AutoOption::Auto, AutoOption::Auto)
550 .set_y_label("Time (µs)", &[])
551 .set_y_grid(true)
552 .set_margins(&[MarginTop(0.8)])
553 .set_legend(Graph(1.0), Graph(1.15), &[], &[])
554 .lines_points(
555 &self.n_threads,
556 &self.population_control_avgs,
557 &[
558 Caption("Average Pop. Control Time"),
559 Color(ColorType::RGBString("#00AA00")),
560 PointSymbol('x'),
561 ],
562 )
563 .lines_points(
564 &self.n_threads,
565 &self.sync_avgs,
566 &[
567 Caption("Average Synchronization Time"),
568 Color(ColorType::RGBString("#AAAA00")),
569 PointSymbol('x'),
570 ],
571 );
572
573 fg.show().unwrap();
574 }
575}
576
577impl From<(&str, &ScalingParams, ScalingType)> for ScalingResults {
579 fn from((root_path, params, scaling_type): (&str, &ScalingParams, ScalingType)) -> Self {
580 let n_threads: Vec<usize> = (0..params.t_iter.unwrap())
582 .map(|idx| params.t_init.unwrap() * params.t_factor.unwrap().pow(idx as u32))
583 .collect();
584 let reports: Vec<TimerReport> = n_threads
585 .iter()
586 .map(|n_thread| {
587 let filename = format!("{}{}.csv", root_path, n_thread);
588 TimerReport::from(File::open(filename).unwrap())
589 })
590 .collect();
591
592 let mut total_exec_times: Vec<f64> = Vec::with_capacity(n_threads.len());
594 let mut population_control_avgs: Vec<f64> = Vec::with_capacity(n_threads.len());
595 let mut tracking_avgs: Vec<f64> = Vec::with_capacity(n_threads.len());
596 let mut tracking_process_avgs: Vec<f64> = Vec::with_capacity(n_threads.len());
597 let mut tracking_sort_avgs: Vec<f64> = Vec::with_capacity(n_threads.len());
598 let mut sync_avgs: Vec<f64> = Vec::with_capacity(n_threads.len());
599
600 reports.iter().for_each(|report| {
601 total_exec_times.push(report[TimerSV::Main].mean);
602 population_control_avgs.push(report[TimerSV::PopulationControl].mean);
603 tracking_avgs.push(report[TimerSV::CycleTracking].mean);
604 tracking_process_avgs.push(report[TimerSV::CycleTrackingProcess].mean);
605 tracking_sort_avgs.push(report[TimerSV::CycleTrackingSort].mean);
606 sync_avgs.push(report[TimerSV::CycleSync].mean);
607 });
608
609 Self {
610 n_threads,
611 total_exec_times,
612 population_control_avgs,
613 tracking_avgs,
614 tracking_process_avgs,
615 tracking_sort_avgs,
616 sync_avgs,
617 scaling_type,
618 }
619 }
620}