Files
2026-10-07 13:06:47 +05:00

166 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
common.benchmark — утилиты для замеров времени и построения таблиц/графиков.
Используется во всех лабораторных (1–7), чтобы отчёты были единообразными.
Пример:
from common.benchmark import time_call, make_table, save_speedup_plot
elapsed, result = time_call(lambda: task["kernel"](chunk), repeats=1)
rows = [("последовательно", T1), ("потоки, p=4", T2)]
print(make_table(rows))
"""
from __future__ import annotations
import os
import statistics
import time
from typing import Callable, Dict, List, Sequence, Tuple
def time_call(fn: Callable, args: tuple = (), repeats: int = 3) -> Tuple[float, any]:
"""Замер времени выполнения fn(*args) с повторами.
Возвращает (лучшее_время_сек, результат). Лучшее (минимальное) время —
стандартная практика: случайные помехи только удлиняют замер.
"""
best = float("inf")
result = None
for _ in range(repeats):
t0 = time.perf_counter()
result = fn(*args)
best = min(best, time.perf_counter() - t0)
return best, result
def speedup(T1: float, Tp: float) -> float:
"""Ускорение S(p) = T(1) / T(p)."""
if Tp <= 0:
return 0.0
return T1 / Tp
def efficiency(speedup_val: float, p: int) -> float:
"""Эффективность E(p) = S(p) / p (доля, а не проценты)."""
if p <= 0:
return 0.0
return speedup_val / p
def amdahl(speedup_bound: float, p: int) -> float:
"""Теоретическое ускорение по закону Амдала.
speedup_bound — доля последовательной части (0..1), т.е. доля кода,
которая не распараллеливается.
"""
s = max(1e-12, speedup_bound)
return 1.0 / (s + (1.0 - s) / p)
def make_table(rows: Sequence[Tuple[str, float]], T1: float = None,
title: str = "Замеры") -> str:
"""Текстовая таблица: подход | T, c | S(p) | E(p).
rows — пары (название, время в секундах). T1 — время последовательного
запуска (первая строка, если не задано отдельно).
"""
if T1 is None:
T1 = rows[0][1]
lines = [
f"{title}",
f"{'подход':<32} {'T, c':>10} {'S(p)':>8} {'E(p)':>8}",
"-" * 62,
]
for name, t in rows:
s = speedup(T1, t)
p = _p_from_name(name)
e = efficiency(s, p)
lines.append(f"{name:<32} {t:>10.3f} {s:>8.2f} {e:>8.2f}")
return "\n".join(lines)
def _p_from_name(name: str) -> int:
"""Пытается вытащить число воркеров из названия ('потоки, p=4' -> 4)."""
if "p=" in name:
try:
return int(name.split("p=")[1].split()[0].strip("),"))
except ValueError:
pass
return 1
def save_speedup_plot(results: Dict[str, List[Tuple[int, float]]], title: str,
out_path: str, show: bool = False):
"""График ускорения S(p) для нескольких подходов.
results = {"потоки": [(1, 1.0), (2, 1.9), (4, 3.8)],
"процессы": [(1, 1.0), (2, 1.95), (4, 3.7)]}
Для каждого подхода S(p) считается относительно его же T(1) — так
кривые корректно сравниваются между собой.
"""
import matplotlib
if not show:
matplotlib.use("Agg")
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
for name, pts in results.items():
pts = sorted(pts)
base = next((t for p, t in pts if p == 1), None)
if base is None:
continue
xs = [p for p, _ in pts]
ys = [base / t for _, t in pts]
plt.plot(xs, ys, marker="o", label=name)
plt.plot(xs, xs, "--", color="gray", label="идеал S(p)=p")
plt.xlabel("число воркеров p")
plt.ylabel("ускорение S(p) = T(1)/T(p)")
plt.title(title)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
plt.savefig(out_path, dpi=150)
if show:
plt.show()
plt.close()
print(f"график сохранён: {out_path}")
def save_bar_plot(results: Dict[str, float], title: str, out_path: str,
ylabel: str = "время, c", show: bool = False):
"""Столбчатая диаграмма: подход -> значение (время или ускорение)."""
import matplotlib
if not show:
matplotlib.use("Agg")
import matplotlib.pyplot as plt
names = list(results.keys())
vals = [results[k] for k in names]
plt.figure(figsize=(9, 5))
plt.bar(names, vals, color="steelblue")
plt.ylabel(ylabel)
plt.title(title)
plt.xticks(rotation=20, ha="right")
plt.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
plt.savefig(out_path, dpi=150)
if show:
plt.show()
plt.close()
print(f"график сохранён: {out_path}")
def median_time(fn: Callable, args: tuple = (), repeats: int = 5) -> Tuple[float, any]:
"""Аналог time_call, но по медиане — устойчив к выбросам."""
times = []
result = None
for _ in range(repeats):
t0 = time.perf_counter()
result = fn(*args)
times.append(time.perf_counter() - t0)
return statistics.median(times), result