2025, Dec 09 15:01
Генерация случайных чисел в NumPy с переиспользованием буфера (out)
Как в NumPy генерировать случайные числа без лишних выделений: используйте Generator с параметром out, чтобы заполнять заранее выделенный буфер быстрее.
Когда вам нужен поток случайных чисел порциями и одновременно используется только одна порция, постоянно создавать новые массивы — расточительно. Обычный вызов возвращает новый массив при каждой итерации, что противоречит цели, если вы хотите переиспользовать заранее выделенный буфер.
Базовый подход, из‑за которого возникает проблема
Прямолинейный способ выделяет новый массив при каждом вызове. Это удобно, но неидеально для плотных циклов, где важно избегать лишних выделений.
import numpy as npx
items = 1000
# Каждый раз создаётся новый массив
chunk = npx.random.randn(items)
Передать аргумент out этому вызову нельзя — такая форма его не поддерживает. Присваивание в заранее выделенный массив через срез, например заполнение буфера из только что сгенерированного массива, всё равно создаёт временный массив справа, так что проблему выделений это тоже не решает.
Что на самом деле происходит
Генератор, который возвращает значения из старого вызова, по задумке каждый раз производит новый массив. Если ваша цель — заполнять существующий массив на месте, нужен API, принимающий целевой буфер. Такой вариант есть: он позволяет передать заранее выделённый массив и заполнять его новыми выборками без создания нового массива для каждой порции.
Есть и нюанс производительности. Базовый вызов довольно медленный, тогда как метод объекта Generator заметно быстрее. Даже так операция упирается в вычисления. На одной конфигурации (i5-9600KF with 2x3600MHz DDR4) использование операций in-place было лишь примерно на 15% быстрее, а больший выигрыш может проявиться в параллельном коде, где генератор вызывается в каждом потоке.
Практическое решение с переиспользованием буфера
Подход ниже переиспользует один и тот же буфер и избегает повторных выделений из порции в порцию.
import numpy as npx
prng = npx.random.default_rng()
length = 1000
buf = npx.empty(length)
prng.standard_normal(length, out=buf)
Это заполняет buf на месте. Повторяйте последнюю строку всякий раз, когда нужна новая порция: один и тот же массив будет перезаписан новыми случайными значениями.
Почему это важно
Когда в памяти одновременно нужна только одна порция, переиспользование заранее выделенного массива позволяет сосредоточиться на вычислениях, а не на выделении памяти. Метод, который пишет прямо в ваш буфер, избегает создания временных массивов и работает быстрее базовой альтернативы. Хотя задача в целом остаётся вычислительно ограниченной, шаблон с записью на месте — простой способ снизить накладные расходы. Если запускать это из нескольких потоков и вызывать генератор в каждом потоке, улучшение может быть заметнее.
Итоги
Для пакетной генерации случайных чисел с переиспользованием памяти предпочтительнее вызывать standard_normal у Generator с параметром out. Это заменяет повторные выделения осознанным переиспользованием буфера, соответствует цели работать лишь с одной порцией за раз и даёт измеримый прирост скорости на практике, пусть и небольшой в одном потоке.