Files
clickstream-data-platform/docs/research/2026-08-01-python-batch-generation-speed.md
T
ddadminandClaude Opus 5 7df5e482b9 docs(specs): правки по холодному ревью — долг словаря и потерянные доводы
- Зачем:
  - холодное ревью (Fable, свежая сессия) нашло невыполненный хвост
    тикета #32 и места, где доводы резолюций сжались до непонятности.
- Что:
  - мастер-спека 1.1: «склад» заменён на «хранилище» (хвост #32);
    CONTEXT.md: DWH в избегаемых, отдельная статья «Пакетный режим».
  - спека: восстановлены доводы «на маке и в WSL тоже» и «менти упрётся
    в красный чек манифеста»; обещания про diff привязаны к манифесту;
    темп ×60 и расчёт порога согласованы с числами разделов; заголовок
    притока честен про затухание; выход за мандат оговорён в «Зачем».
  - раздел 9: добавлены числа притока и календарная дата-константа D0;
    заметка исследования: у Faker единицы «значений/с», не «строк/с».
- Проверка:
  - вычитка; решения развилок не пересматриваются, правки текстовые.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 21:00:18 +03:00

11 KiB
Raw Blame History

Скорость батчевой генерации событийных данных в Python: порядки величин

Дата: 2026-08-01. Тикет: #31 (часть карты #26). Потребитель: развилка производительности генератора (#30, спека v2, разделы 9 и 11).

Вопрос

Какой скорости (строк или событий в секунду) реально ждать от батчевой генерации событий в Python и что для неё берут: numpy/векторная генерация против посточной, orjson против stdlib json, multiprocessing по дням? Ответ — по первоисточникам (документация и бенчмарки авторов библиотек).

Краткий вывод

Порядки величин на одно ядро CPU:

Подход Порядок скорости Опора
Случайные значения numpy (векторно) ~10^8 значений/с таблица numpy
Векторная сборка колонок события ~10^610^7 строк/с локальная проверка
Посточная генерация (random + dict) ~10^410^5 строк/с локальная проверка
Посточная генерация через Faker ~10^3–10^4 значений/с бенчмарк mimesis
Сериализация orjson (запись ~50 КБ) ~10^5 док/с README orjson
Сериализация stdlib json (та же запись) ~10^4 док/с README orjson

Ключевые множители: векторная генерация быстрее посточной на 1–2 порядка; orjson быстрее stdlib json в 5–14 раз на сериализации; multiprocessing умножает всё на число ядер, потому что обходит GIL подпроцессами.

Узкое место конвейера «сгенерировать → JSON в Kafka» — не случайные числа (они почти бесплатны), а сборка посточных словарей и их сериализация: это ~2–5×10^5 событий/с на ядро, дальше масштабирование только процессами.

Находки по первоисточникам

1. orjson против stdlib json — бенчмарки автора

Источник: README orjson, раздел Performance — https://github.com/ijl/orjson#performance. Среда автора: Python 3.11.10, Fedora 42, x86-64-v4; скрипт pybench в репозитории.

Сериализация (медиана, операций в секунду):

Файл orjson json Ускорение
twitter.json (~600 КБ) 8 453 765 11,1x
github.json (~55 КБ) 103 693 7 648 13,6x
citm_catalog.json 3 975 338 11,8x
canada.json 399 33 11,9x

Десериализация ускоряется скромнее: 2,2–6x на тех же файлах.

Отдельно важное для генератора: orjson сериализует numpy.ndarray нативно (опция OPT_SERIALIZE_NUMPY). Числа автора: массив float64 на 92 МиБ — 105 мс против 1 481 мс у json (14,2x); int32 на 100 МиБ — 68 мс против 684 мс (10,1x). datetime тоже сериализуется нативно в RFC 3339, без обёрток и default=.

Документация stdlib json заявлений о скорости не делает вовсе (https://docs.python.org/3/library/json.html). Факт «чистый Python с C-ускорителем» виден только в исходниках CPython: Lib/json/encoder.py импортирует c_make_encoder из _json с откатом на Python-реализацию (https://github.com/python/cpython/blob/main/Lib/json/encoder.py).

2. Векторная генерация случайных значений — документация numpy

Источник: официальная страница производительности генераторов — https://numpy.org/doc/stable/reference/random/performance.html. Среда: Linux, AMD Ryzen 9 3900X. Единицы — наносекунды на одно значение.

Распределение PCG64 SFC64 MT19937
uint32 1,9 1,8 3,3
uint64 3,2 2,5 5,6
равномерное 3,1 2,6 5,9
нормальное 10,8 8,3 13,9
пуассоновское 103,4 90,7 111,7

То есть 3 нс на равномерное значение — это ~3×10^8 значений/с на ядро. Даже для широкого события в ~47 колонок чистая генерация значений даёт миллионы строк в секунду: не она ограничивает конвейер. Рекомендация numpy — PCG64 (или PCG64DXSM для сильно параллельных сценариев).

3. multiprocessing — документация Python

Источник: https://docs.python.org/3/library/multiprocessing.html. Дословно: пакет «effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads» — то есть генерация по независимым модельным дням масштабируется числом ядер, а не упирается в GIL.

Практические указания из тех же доков: Pool.map режет итерируемое на куски (chunksize), а для длинных последовательностей «using a large value for chunksize can make the job complete much faster than using the default value of 1» (про imap). Для нашей схемы «один процесс — один день» это неактуально: единица работы и так крупная.

4. Faker и цена посточной генерации

Сам Faker чисел не публикует, но признаёт цену взвешенного выбора: конструктор принимает use_weighting, и при False «the selection process is much faster» (https://faker.readthedocs.io/en/master/index.html, раздел Optimizations).

Числа даёт авторский бенчмарк mimesis против Faker — https://mimesis.name/master/benchmarks.html (mimesis 19.0.0, MacBook Pro M1 Pro). На 47 сопоставимых операциях mimesis выиграл все сравнения, суммарное ускорение ~24x; по категориям — от 5x (Datetime) до 69x (Finance). Порядок абсолютных цифр: сотни микросекунд на вызов Faker — то есть ~10^3–10^4 значений/с. Оговорка: единицы в таблицах страницы внутренне противоречивы (µs против ms не сходятся с заявленным 24x), надёжны именно коэффициенты ускорения, абсолютные значения — с осторожностью.

Вывод для нас: Faker в горячем цикле на десятки миллионов событий — это узкое место на 2–3 порядка хуже numpy. Его место — генерация небольших справочников (каталог, имена), не потока событий.

5. Кросс-проверка: msgspec

Бенчмарки автора msgspec (https://msgspec.dev/benchmarks, CPython 3.11): msgspec со схемами быстрее всех, без схем — «on-par with orjson (the next fastest JSON library)». Это подтверждает, что orjson — верхняя планка скорости JSON в Python без введения схем; выигрыш msgspec — скорее память (на декоде 77 МиБ: 67,6 МиБ пика против 406,3 МиБ у orjson).

Локальная проверка порядков (не первоисточник)

Микробенчмарк на машине стенда (8 логических ядер, Python 3.14, uv run --with numpy --with orjson), 200 тыс. строк по 15 колонок:

  • numpy векторно: ~6,4 млн строк/с;
  • посточно (stdlib random + dict): ~71 тыс. строк/с — разрыв ~90x;
  • orjson.dumps по строке: ~560 тыс. строк/с; json.dumps: ~120 тыс. (4,7x);
  • транспонирование «колонки → посточные dict» (нужно для JSON в Kafka): ~275 тыс. строк/с — именно оно, а не RNG, становится узким местом векторного пути;
  • multiprocessing, 4 процесса на независимых «днях»: x1,9 на маленькой нагрузке (старт пула съедает долю; на крупных днях доля падает).

Локальные числа сходятся с первоисточниками по порядку величины: разрыв orjson/json на маленьких записях меньше README-шного (4–5x против 11–14x — у автора записи крупнее), остальное совпадает.

Что это значит для генератора стенда

Без принятия решений — решает тикет #30; здесь только опора для него.

  • Реалистичная планка конвейера «генерация + orjson посточно» — ~2–5×10^5 событий/с на ядро, с multiprocessing по дням — умножить на число ядер. Например, 10 млн событий — это десятки секунд на одном ядре и секунды на восьми.
  • Случайные числа брать векторно из numpy (PCG64) — их стоимость можно не учитывать в бюджете. Посточный цикл оставить только там, где логика действительно посточная (цепочки сессий), и не звать в нём Faker.
  • Живой поток ×60: даже 100 тыс. событий модельного дня при ×60 — это ~70 событий/с реального времени; на 3–4 порядка ниже планки, скоростью не ограничен.
  • Довод спеки «компилируемый язык только если замеры покажут, что Python не тянет» получает численную опору: до ~10^6 событий/с суммарно Python с батчевой архитектурой укладывается, дальше — территория Rust/Go.