116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Лабораторная работа 3: GIL — почему потоки не ускоряют CPU-код.
|
|
|
|
Заполните функции ниже. ИНТЕРФЕЙСЫ МЕНЯТЬ НЕЛЬЗЯ — по ним работают
|
|
автотесты (tests/test_lab03.py).
|
|
|
|
Запуск из корня курса:
|
|
python lab03_gil/solution.py
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from common.benchmark import save_speedup_plot, time_call
|
|
from common.tasks import TASKS, get_variant
|
|
|
|
# TODO: впишите свой номер в журнале (1..20)
|
|
VARIANT_NUMBER = 0
|
|
|
|
|
|
def run_task_on_threads(task_name: str, params: dict, p: int):
|
|
"""Выполнить CPU-задачу на p потоках (ThreadPoolExecutor).
|
|
|
|
Каждый поток получает свой кусок (task['split']) и выполняет task['kernel'].
|
|
Вернуть (checksum, elapsed_seconds). checksum обязан совпасть с
|
|
последовательным запуском — иначе разбиение/сборка неверны.
|
|
|
|
Подсказка: потоки, в отличие от процессов, не требуют пиккелируемости —
|
|
task["kernel"] можно вызывать прямо в воркере.
|
|
"""
|
|
task = TASKS[task_name]
|
|
# TODO: build -> split -> pool.map(kernel, chunks) -> combine -> checksum
|
|
raise NotImplementedError
|
|
|
|
|
|
def measure_cpu_threads(task_name: str, params: dict, p_list):
|
|
"""Замеры T(p) на потоках для чистого-Python задачи.
|
|
|
|
Вернуть dict {p: лучшее_время}. p=1 — база. Проверять совпадение
|
|
checksum при каждом p!
|
|
"""
|
|
# TODO: для каждого p -> time_call(run_task_on_threads, ..., repeats=3)
|
|
raise NotImplementedError
|
|
|
|
|
|
def measure_gil_release(p_list, block_size=1 << 20, blocks_per_part=32):
|
|
"""Замеры T(p) для hashlib (освобождает GIL на длинных вызовах).
|
|
|
|
Готовит данные заранее: W = blocks_per_part * max(p_list) блоков по
|
|
block_size байт (os.urandom). Общий объём W делится между p потоками —
|
|
как в CPU-замерах. Вернуть dict {p: лучшее_время}, checksum проверять.
|
|
|
|
ВАЖНО: блоки должны быть большими (≥ 256 КБ). На коротких вызовах GIL
|
|
возвращается мгновенно и ускорение пропадает. Сравните block_size=4096
|
|
и block_size=1<<20 в отчёте!
|
|
"""
|
|
# TODO: данные -> деление на p кусков -> ThreadPoolExecutor ->
|
|
# hashlib.sha256(блок).digest() -> checksum -> {p: время}
|
|
raise NotImplementedError
|
|
|
|
|
|
def gil_status():
|
|
"""Вернуть (версия_python, gil_enabled: bool).
|
|
|
|
Используйте sys._is_gil_enabled() (Python 3.13+); если атрибута нет,
|
|
считайте GIL включённым.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def main():
|
|
v = get_variant(VARIANT_NUMBER)
|
|
print(f"Вариант {v['variant']}: {v['describe']}\n")
|
|
|
|
ver, gil = gil_status()
|
|
print(f"Python {ver}, GIL включён: {gil}\n")
|
|
|
|
# --- 1. Чистый Python: S(p) ≈ 1 ---------------------------------------
|
|
t_cpu = measure_cpu_threads(v["task_name"], v["cpu_params"], v["p_list"])
|
|
print("Чистый Python (GIL не отпускается):")
|
|
base = t_cpu[1]
|
|
for p, t in sorted(t_cpu.items()):
|
|
print(f" p={p}: T={t:.3f} c S={base / t:.2f}")
|
|
|
|
# --- 2. hashlib: S(p) > 1 ---------------------------------------------
|
|
t_hash = measure_gil_release(v["p_list"])
|
|
print("\nhashlib SHA-256 (GIL отпускается, блоки 1 МБ):")
|
|
base = t_hash[1]
|
|
for p, t in sorted(t_hash.items()):
|
|
print(f" p={p}: T={t:.3f} c S={base / t:.2f}")
|
|
|
|
# --- 2b. Мелкие блоки: ускорение должно исчезнуть ----------------------
|
|
t_small = measure_gil_release(v["p_list"], block_size=4096,
|
|
blocks_per_part=8000)
|
|
print("\nhashlib SHA-256 (блоки 4 КБ — GIL возвращается сразу):")
|
|
base = t_small[1]
|
|
for p, t in sorted(t_small.items()):
|
|
print(f" p={p}: T={t:.3f} c S={base / t:.2f}")
|
|
|
|
# --- 3. График ----------------------------------------------------------
|
|
save_speedup_plot(
|
|
{"чистый Python": sorted(t_cpu.items()),
|
|
"hashlib 1 МБ (released GIL)": sorted(t_hash.items()),
|
|
"hashlib 4 КБ": sorted(t_small.items())},
|
|
f"GIL: ускорение потоками (Python {ver})",
|
|
os.path.join(os.path.dirname(__file__), "speedup.png"))
|
|
|
|
# TODO: в отчёт — таблицу серий, график, объяснение разницы кривых,
|
|
# ответы на контрольные вопросы из методички.
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |