# -*- 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()