Files
parallel-programming/lab07_comparison/solution.py
T
2026-10-07 13:06:47 +05:00

163 lines
6.9 KiB
Python
Raw 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 -*-
"""Лабораторная работа 7 (итоговая): одна задача — четыре подхода.
Заполните функции ниже. ИНТЕРФЕЙСЫ МЕНЯТЬ НЕЛЬЗЯ — по ним работают
автотесты (tests/test_lab07.py, запускаются через подпроцесс).
Запуск из корня курса:
python lab07_comparison/solution.py
"""
import asyncio
import json
import multiprocessing
import os
import sys
import time
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from common.benchmark import amdahl, make_table, save_speedup_plot, time_call
from common.tasks import TASKS, dispatch_kernel, get_variant, io_fetch
# TODO: впишите свой номер в журнале (1..20)
VARIANT_NUMBER = 0
# ========================= CPU-сценарий ====================================
def cpu_sequential(task_name: str, params: dict):
"""Последовательно (лаба 1). Вернуть (checksum, сек)."""
task = TASKS[task_name]
# TODO: build -> замер времени -> split(data, 1) -> kernel -> combine -> checksum
raise NotImplementedError
def cpu_threads(task_name: str, params: dict, p: int):
"""На p потоках (лаба 3): ожидание S ≈ 1 из-за GIL. Вернуть (checksum, сек)."""
# TODO: как в лабе 3 — ThreadPoolExecutor, kernel напрямую в воркере
raise NotImplementedError
def cpu_processes(task_name: str, params: dict, p: int):
"""На p процессах (лаба 4): ожидание S ≈ ядра. Вернуть (checksum, сек)."""
# TODO: как в лабе 4 — ctx.Pool + dispatch_kernel
raise NotImplementedError
# ========================= I/O-сценарий ====================================
def io_sequential(items: int, delay: float):
"""Последовательно (лаба 1). Вернуть (список, сек)."""
# TODO: [io_fetch(i, delay) for i in range(items)] с замером
raise NotImplementedError
def io_threads(items: int, delay: float, p: int):
"""На p потоках (лаба 2). Вернуть (список, сек)."""
# TODO: ThreadPoolExecutor + pool.map(io_fetch, ..., [delay]*items)
raise NotImplementedError
async def _io_async_coro(items: int, delay: float):
"""Корутина (лаба 6): gather + asyncio.sleep. Вернуть (список, сек)."""
raise NotImplementedError
def io_async(items: int, delay: float):
"""Обёртка asyncio.run. Вернуть (список, сек)."""
raise NotImplementedError
def io_async_limited(items: int, delay: float, max_concurrent: int = 8):
"""Async с семафором (лаба 6). Вернуть (список, сек)."""
raise NotImplementedError
# ========================= main ============================================
def _smoke():
"""Smoke-режим автотестов: быстрый прогон всех подходов, JSON-отчёт.
(не удаляйте: тесты запускают solution.py подпроцессом с LAB_SMOKE=1)"""
from common.tasks import smoke_params, run_sequential
name, sp = "integrate", smoke_params("integrate")
exp, _ = run_sequential(name, sp, parts=1)
cs1, _ = cpu_threads(name, sp, 2)
cs2, _ = cpu_processes(name, sp, 2)
ok_cpu = (cs1 == exp or abs(cs1 - exp) < 1e-6) and \
(cs2 == exp or abs(cs2 - exp) < 1e-6)
res, t_seq = io_sequential(8, 0.0)
res2, t_thr = io_threads(8, 0.02, 4)
res3, t_asy = io_async(8, 0.02)
ok_io = len(res) == 8 and len(res2) == 8 and len(res3) == 8
print("LAB_SMOKE_JSON:" + json.dumps({
"ok": True, "cpu_ok": bool(ok_cpu), "io_ok": bool(ok_io),
"p": 2, "io_times": {"seq": t_seq, "threads": t_thr, "async": t_asy}}))
return
def main():
if os.environ.get("LAB_SMOKE") == "1":
_smoke()
return
v = get_variant(VARIANT_NUMBER)
items, delay = v["io"]["items"], v["io"]["delay"]
print(f"Вариант {v['variant']}: {v['describe']}")
print(f"I/O: {items} запросов по {delay} c\n")
# --- CPU-сценарий -------------------------------------------------------
print("=== CPU-сценарий ===")
cs0, t_seq = cpu_sequential(v["task_name"], v["cpu_params"])
print(f"последовательно: {t_seq:.3f} c")
cpu_results = {"потоки": [], "процессы": []}
for p in v["p_list"]:
_, (c1, t_thr) = time_call(cpu_threads,
(v["task_name"], v["cpu_params"], p), repeats=2)
_, (c2, t_proc) = time_call(cpu_processes,
(v["task_name"], v["cpu_params"], p), repeats=2)
assert (c1 == cs0 or abs(c1 - cs0) < 1e-6) and \
(c2 == cs0 or abs(c2 - cs0) < 1e-6), f"checksum не совпал при p={p}"
cpu_results["потоки"].append((p, t_thr))
cpu_results["процессы"].append((p, t_proc))
print(f"p={p}: потоки {t_thr:.3f} c (S={t_seq/t_thr:.2f}) | "
f"процессы {t_proc:.3f} c (S={t_seq/t_proc:.2f})")
# --- I/O-сценарий ---------------------------------------------------------
print("\n=== I/O-сценарий ===")
_, t_ioseq = io_sequential(items, delay)
_, t_iothr = io_threads(items, delay, 8)
_, t_ioasy = io_async(items, delay)
_, t_ioasy_lim = io_async_limited(items, delay, 8)
io_rows = [("последовательно", t_ioseq),
("потоки, p=8", t_iothr),
("async", t_ioasy),
("async, лимит 8", t_ioasy_lim)]
print(make_table(io_rows, title="I/O-сценарий"))
# --- График CPU ------------------------------------------------------------
save_speedup_plot(cpu_results,
f"CPU: ускорение ({v['describe']})",
os.path.join(os.path.dirname(__file__), "speedup_cpu.png"))
# --- Оценка s по Амдалу -----------------------------------------------------
p_max = max(v["p_list"])
s_meas = t_seq / cpu_results["процессы"][-1][1]
best_s, best_diff = None, float("inf")
for i in range(1, 200): # перебираем s = 0.005 .. 0.995
s = i / 200
diff = abs(amdahl(s, p_max) - s_meas)
if diff < best_diff:
best_diff, best_s = diff, s
print(f"\nИзмеренное S({p_max}) = {s_meas:.2f}; "
f"по закону Амдала это соответствует s ≈ {best_s:.3f} "
f"(доля последовательного кода с накладными расходами)")
# TODO: в отчёт — обе таблицы, график, объяснение s, развёрнутые выводы
# по всему курсу (это итоговая работа).
if __name__ == "__main__":
main()