From f70087c4389d9a34c87e94d485a376e6c09b4d26 Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Sat, 1 Aug 2026 15:54:28 +0300 Subject: [PATCH] =?UTF-8?q?docs(research):=20=D1=81=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B1=D0=B0=D1=82=D1=87=D0=B5=D0=B2?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B2=20Python=20=E2=80=94=20=D0=BF=D0=BE=D1=80=D1=8F?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=20=D0=B2=D0=B5=D0=BB=D0=B8=D1=87=D0=B8=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: развилке производительности генератора (#30) нужны числа вместо догадок — тикет #31 просил ответ по первоисточникам. Что: заметка docs/research/2026-08-01-python-batch-generation-speed.md — бенчмарки авторов orjson (5–14x к stdlib json), таблица производительности генераторов numpy (~3 нс на значение), доки multiprocessing (обход GIL), бенчмарк mimesis против Faker (~24x), кросс-проверка msgspec; плюс локальная проверка порядков через uv. Проверка: ссылки на первоисточники в тексте; локальный микробенчмарк воспроизводится `uv run --with numpy --with orjson`. Co-Authored-By: Claude Opus 5 --- ...026-08-01-python-batch-generation-speed.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/research/2026-08-01-python-batch-generation-speed.md diff --git a/docs/research/2026-08-01-python-batch-generation-speed.md b/docs/research/2026-08-01-python-batch-generation-speed.md new file mode 100644 index 0000000..f771856 --- /dev/null +++ b/docs/research/2026-08-01-python-batch-generation-speed.md @@ -0,0 +1,159 @@ +# Скорость батчевой генерации событийных данных в Python: порядки величин + +Дата: 2026-08-01. Тикет: #31 (часть карты #26). Потребитель: развилка +производительности генератора (#30, спека v2, разделы 9 и 11). + +## Вопрос + +Какой скорости (строк или событий в секунду) реально ждать от батчевой +генерации событий в Python и что для неё берут: numpy/векторная генерация +против посточной, orjson против stdlib json, multiprocessing по дням? +Ответ — по первоисточникам (документация и бенчмарки авторов библиотек). + +## Краткий вывод + +Порядки величин на одно ядро CPU: + +| Подход | Порядок скорости | Опора | +|---|---|---| +| Случайные значения numpy (векторно) | ~10^8 значений/с | таблица numpy | +| Векторная сборка колонок события | ~10^6–10^7 строк/с | локальная проверка | +| Посточная генерация (random + dict) | ~10^4–10^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 — +. Среда автора: 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 заявлений о скорости не делает вовсе +(). Факт «чистый Python +с C-ускорителем» виден только в исходниках CPython: `Lib/json/encoder.py` +импортирует `c_make_encoder` из `_json` с откатом на Python-реализацию +(). + +### 2. Векторная генерация случайных значений — документация numpy + +Источник: официальная страница производительности генераторов — +. +Среда: 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 + +Источник: . +Дословно: пакет «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» (, +раздел Optimizations). + +Числа даёт авторский бенчмарк mimesis против Faker — + (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 (, 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.