| 58 | |
| 59 | |
| 60 | def plot_histograms( |
| 61 | per_threads: Dict[int, List[float]], out_dir: Path, bins: int, mode: str |
| 62 | ) -> None: |
| 63 | out_dir.mkdir(parents=True, exist_ok=True) |
| 64 | |
| 65 | filtered: List[tuple[int, List[float]]] = [] |
| 66 | for threads, values in per_threads.items(): |
| 67 | positive_values = [value for value in values if value > 0.0] |
| 68 | if not positive_values: |
| 69 | continue |
| 70 | filtered.append((threads, positive_values)) |
| 71 | |
| 72 | if not filtered: |
| 73 | return |
| 74 | |
| 75 | global_min = min(min(values) for _, values in filtered) |
| 76 | global_max = max(max(values) for _, values in filtered) |
| 77 | if global_min == global_max: |
| 78 | global_min *= 0.5 |
| 79 | global_max *= 2.0 |
| 80 | |
| 81 | bin_edges = np.logspace(np.log10(global_min), np.log10(global_max), bins + 1) |
| 82 | |
| 83 | fig, axes = plt.subplots( |
| 84 | nrows=len(filtered), |
| 85 | ncols=1, |
| 86 | figsize=(9, 3.2 * len(filtered)), |
| 87 | sharex=True, |
| 88 | squeeze=False, |
| 89 | ) |
| 90 | |
| 91 | for ax, (threads, values) in zip(axes.flat, filtered): |
| 92 | ax.hist(values, bins=bin_edges, color="#1f77b4", alpha=0.85) |
| 93 | ax.set_xscale("log") |
| 94 | ax.set_ylabel("count") |
| 95 | ax.set_title(f"threads={threads} (n={len(values)})") |
| 96 | |
| 97 | axes[-1, 0].set_xlabel("total time / page (s)") |
| 98 | fig.suptitle(f"Per-page total timing histograms — mode={mode}", y=0.995) |
| 99 | fig.tight_layout() |
| 100 | fig.savefig(out_dir / "hist_stacked.png", dpi=160) |
| 101 | plt.close(fig) |
| 102 | |
| 103 | |
| 104 | def main() -> int: |