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

+163
View File
@@ -0,0 +1,163 @@
# -*- 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()
+110
View File
@@ -0,0 +1,110 @@
# Лабораторная работа 7. Сравнительный анализ: одна задача — четыре подхода
**Время на работу:** 2 пары. **Зависимости:** лабы 1–6. Итоговая работа.
## Цель работы
Свести весь курс в одну таблицу: ваша CPU-задача и ваша I/O-нагрузка,
выполненные последовательно, потоками, процессами и асинхронно. Объяснить
каждую цифру. Это работа для защиты: по ней проверяется понимание всего
курса.
## Теория
### Ускорение и эффективность
- Ускорение: **S(p) = T(1) / T(p)** — во сколько раз быстрее на p воркерах.
- Эффективность: **E(p) = S(p) / p** — насколько эффективно используется
каждый воркер. E = 1 — идеал, E = 0.5 — половина мощности простаивает.
### Закон Амдала
Если доля последовательного кода задачи равна s (0 ≤ s ≤ 1), то ускорение
ограничено:
```
S(p) ≤ 1 / (s + (1 − s) / p)
```
При s = 0.1 и p → ∞: S ≤ 10. Даже бесконечное число воркеров не даст
больше 10×. Отсюда главный вывод курса: **сначала сокращайте
последовательную часть и накладные расходы, потом наращивайте p.**
В `common.benchmark` есть `amdahl(s, p)` — сравните свои S(p) с теорией:
зная измеренное S(p), можно оценить эффективную долю последовательной части
вашего эксперимента (создание пулов, pickle, GIL-переключения — всё это
«последовательная» часть).
### Матрица выбора модели
| Задача | Потоки | Процессы | asyncio |
|---|---|---|---|
| CPU-bound (чистый Python) | ✗ (GIL) | ✓ | ✗ (блокирует loop) |
| CPU-bound в C-коде (hashlib, numpy) | ✓ | ✓ | ✗ |
| I/O-bound, десятки задач | ✓ | возможно (дорого) | ✓ |
| I/O-bound, тысячи задач | дорого | ✗ | ✓✓ |
## Задание
Заполните `solution.py` — оба сценария из ваших лабораторных:
**CPU-сценарий** (задача из варианта):
1. `cpu_sequential(task_name, params)` — как в лабе 1;
2. `cpu_threads(task_name, params, p)` — как в лабе 3 (S ≈ 1!);
3. `cpu_processes(task_name, params, p)` — как в лабе 4 (S ≈ ядра).
**I/O-сценарий** (items × delay из варианта):
4. `io_sequential(items, delay)` — как в лабе 1;
5. `io_threads(items, delay, p)` — как в лабе 2;
6. `io_async(items, delay)` — как в лабе 6;
7. `io_async_limited(items, delay, max_concurrent)` — с семафором 8.
**`main()`** — таблицы для обоих сценариев с T(p), S(p), E(p); два графика
ускорения; оценка доли s по закону Амдала для процессов.
### Ожидаемые результаты (сверьтесь)
CPU: потоки S ≈ 1; процессы S(p) ≈ до числа физических ядер.
I/O: последовательный — база; потоки и async — S ≈ p; async без лимита —
минимальное время (нет расходов на потоки); processes для I/O — работает,
но с заметными накладными расходами.
### Что дополнительно оценить
- Разница T(1) у процессов против последовательного — цена старта процессов
и pickle.
- По S(p_max) процессов найдите s через `amdahl` (подберите s так, чтобы
теория совпала с практикой) — укажите его в отчёте.
## Контрольные вопросы
1. Почему потоки не ускорили CPU-задачу, но ускорили I/O?
2. Почему asyncio на I/O обгоняет потоки? Откуда выигрыш?
3. Какая доля последовательного кода получилась у процессов по Амдалу?
Что в неё входит?
4. Какую модель вы выбрали бы для веб-краулера на 10 000 страниц?
Для рендера видео? Обоснуйте.
5. Что в ваших замерах не совпало с теорией и почему?
## Что сдаётся
`solution.py` + отчёт: две таблицы (CPU и I/O) с T/S/E, два графика,
оценка s по Амдалу, развёрнутые выводы (это итоговая работа — выводы
должны покрывать весь курс), ответы на вопросы.
## Критерии оценки
| Пункт | Баллы |
|---|---|
| Все 7 функций работают, checksum корректен | 3 |
| Таблицы и графики для обоих сценариев | 3 |
| Оценка s по закону Амдала с объяснением | 2 |
| Выводы и ответы на вопросы (защита) | 4 |
## Типичные ошибки
- Сравнение T(p) разных сценариев при разном объёме работы — объём
фиксирован внутри сценария.
- Забыт smoke-режим `LAB_SMOKE` (автотесты запускают solution.py
подпроцессом — не удаляйте блок в main()).
- Замеры в один запуск без повторов: `time_call(..., repeats=3)`.