81 lines
3.3 KiB
Python
81 lines
3.3 KiB
Python
# -*- 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() |