Когда несколько потоков читают одни и те же данные, первое, что приходит в голову — скопировать каждый кусок отдельно. Потом смотришь на профилировщик и видишь, что 40% времени ушло на эти копирования. В production это убивает производительность в задачах обработки видео, аудио или бинарных протоколов.
Буферный протокол и memoryview
Memoryview и буферный протокол numpy позволяют читать одни и те же данные из разных потоков без единого лишнего байта. Берём bytearray на миллион байт, создаём memoryview, режем на куски и отдаём потокам. Каждый поток через np.frombuffer получает ndarray, который смотрит ровно в ту же память.
import numpy as np
import threading
shared_data = bytearray(1_000_000)
shared_view = memoryview(shared_data)
def process_chunk(offset, size):
chunk = np.frombuffer(shared_view[offset:offset+size], dtype=np.uint8)
chunk[:] = (chunk * 2 + 10) % 256
threads = []
chunk_size = 100_000
for i in range(0, len(shared_data), chunk_size):
t = threading.Thread(target=process_chunk, args=(i, chunk_size))
threads.append(t)
t.start()
for t in threads:
t.join()
Ключевой момент: GIL снимается, когда numpy вызывает C-код. Поэтому CPU-bound задачи с numpy действительно ускоряются в потоках, и не надо сразу лезть в multiprocessing.
Ограничения и типичная ошибка
Memoryview работает только с contiguous буферами. Если массив со stride — приходится делать np.ascontiguousarray, а это уже копия. По опыту, чаще всего данные из файлов или сети идут подряд, так что проблема не смертельная. Типичная ошибка — забыть проверить, что буфер contiguous, и получить неявную копию.
Оптимизация для numpy
Если данные уже лежат в numpy, можно не создавать memoryview. У ndarray есть буферный протокол, и np.frombuffer скушает его напрямую. Меньше телодвижений, но проверка на contiguous всё равно нужна.
Где это выстреливает в production
Это реально ускоряет: обработка видео в реальном времени, аудиофреймы, высокочастотные тикеры, разбор бинарных протоколов, чтение больших HDF5 и FASTQ. Везде, где данных много, а копировать их больно.
Вывод:
Нулевое копирование через memoryview и буферы numpy — ключ к ускорению многопоточных in-memory задач, но только с contiguous буферами и пониманием, что GIL снимается в C-коде.