# -*- coding: utf-8 -*- """Демо: hashlib освобождает GIL — потоки дают настоящее ускорение. Запуск из корня курса: python lab03_gil/examples/02_hashlib_gil_demo.py hashlib.sha256 отпускает GIL на время хеширования ОДНОГО блока. На коротких блоках (4 КБ) вызовы слишком быстрые — GIL забирается обратно, ускорения нет. На больших блоках (1 МБ) вызовы длинные — потоки работают параллельно. """ import hashlib import os import sys import time from concurrent.futures import ThreadPoolExecutor sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) # Три набора данных (для hashlib — одинаковый суммарный объём, разные блоки) PY_DATA = [os.urandom(256 * 1024) for _ in range(16)] # 4 МБ, для чистого Python HASH_BIG = [os.urandom(1 << 20) for _ in range(48)] # 48 МБ блоками по 1 МБ HASH_SMALL = [os.urandom(4096) for _ in range(48 * 256)] # те же 48 МБ блоками по 4 КБ MASK = 0xFFFFFFFFFFFFFFFF def py_checksum(chunk): """Чистый Python: GIL не отпускает.""" acc = 0 for b in chunk: for x in b: # питоновский цикл по байтам acc = (acc + x * 31) & MASK return acc def hash_checksum(chunk): """C-функция hashlib: GIL отпускается на время хеширования блока.""" acc = 0 h = hashlib.sha256 for b in chunk: acc = (acc + int.from_bytes(h(b).digest()[:8], "big")) & MASK return acc def run_threads(fn, data, parts): bounds = [i * len(data) // parts for i in range(parts + 1)] chunks = [data[bounds[i]:bounds[i + 1]] for i in range(parts)] with ThreadPoolExecutor(max_workers=parts) as pool: return list(pool.map(fn, chunks)) def min_time(fn, data, parts, repeats=3): best = float("inf") for _ in range(repeats): t0 = time.perf_counter() run_threads(fn, data, parts) best = min(best, time.perf_counter() - t0) return best def bench(label, fn, data): acc1 = sum(run_threads(fn, data, 1)) & MASK acc4 = sum(run_threads(fn, data, 4)) & MASK assert acc1 == acc4, "результат зависит от разбиения — ошибка!" t1 = min_time(fn, data, 1) t4 = min_time(fn, data, 4) print(f"{label:<26} T(1)={t1:.3f} c T(4)={t4:.3f} c S(4)={t1 / t4:.2f}") def main(): print("hashlib: одинаковый суммарный объём 48 МБ, разные размеры блоков\n") bench("чистый Python", py_checksum, PY_DATA) bench("hashlib, блоки 4 КБ", hash_checksum, HASH_SMALL) bench("hashlib, блоки 1 МБ", hash_checksum, HASH_BIG) print("\nS(4)≈1 у чистого Python и мелких блоков против S(4)≈ядрам у") print("больших блоков. GIL отпускается на время ОДНОГО вызова C-функции —") print("вызовы должны быть длинными.") if __name__ == "__main__": main()