This commit is contained in:
lovinervy committed 2026-10-07 13:06:47 +05:00
commit c394677d07
59 files changed
+5149

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
# -*- 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