Бенчмарки

Точность против наивной арифметики — и цена этой точности

Всё на этой странице получено одним прогоном скрипта scripts/generate-benchmarks.py 2026-09-08. Никаких «до N раз точнее» без прогона: где Balansis выигрывает — показан выигрыш, где проигрывает — показан проигрыш, в тех же единицах.

Как это меряется

  • Эталон точности — рациональная арифметика. Ошибка каждого метода считается против значения, вычисленного в fractions.Fraction, а не против «более длинного float». Иначе эталон сам был бы участником соревнования. Единственное место, где рациональной арифметики недостаточно, — softmax: экспонента там считается в decimal с 60 значащими цифрами (на 40+ порядков точнее float64), а суммирование и деление — там же.
  • Конкуренты выбраны по-честному. В каждом сценарии рядом с Balansis стоят не только наивный цикл, но и math.fsum, встроенный sum() и numpy — то есть то, что реально есть у инженера под рукой.
  • Время — минимум из серии. Хост разделяемый (Intel Xeon (серверный), 8 vCPU), поэтому headline-значение — наименьшее время из повторов, как наименее зашумлённое; медиана лежит рядом в JSON. Абсолютные значения зависят от машины, отношения между методами устойчивы — но только после поправки из следующего пункта.
  • Накладные вызова вычтены. Обвязка измерения вызывает питоновскую функцию на каждую операцию; пустой вызов в ней стоит 15.2 нс — столько же, сколько само сложение float. Поэтому в таблицах цены обе колонки уменьшены на эту величину, а отношение «как есть» показано отдельной строкой. Проверка: сложение float в скомпилированном цикле timeit без вызова на операцию даёт 10.5 нс против 11.0 нс после вычитания.
  • Проверяется библиотека с PyPI, а не рабочая копия. Прогон идёт в чистом venv, куда установлено колесо balansis 1.1.0; sha256 скачанного файла сверен с digest, который объявляет JSON-API PyPI (2445ae239cb21e48, совпал).

Точность: сводка по сценариям

Верных десятичных знаков в ответе (16 — предел float64, 0 — ответ потерян целиком). Измерено 2026-09-08
СценарийМетодВерных знаковАбс. ошибкаВремя (min)
Сумма 300 слагаемых со взаимным уничтожениемнаивный цикл float640.01003.11 мкс
встроенный sum()16 (предел float64)01.11 мкс
numpy.sum (попарное суммирование)0.0918.32 мкс
math.fsum16 (предел float64)01.57 мкс
Balansis Operations.sequence_sum16 (предел float64)039.31 мкс
Скалярное произведение, cond ≈ 5e18наивный цикл float640.031.469437.81 мкс
numpy.dot (BLAS)0.031.4694643.0 нс
math.fsum над произведениями0.07.8444151.89 мкс
Balansis dot2 (Ogita–Rump–Oishi)16 (предел float64)4.478e-1787.65 мкс
Balansis numpy_integration.compensated_dot_product16 (предел float64)4.478e-1787.57 мкс
Единственный сценарий с чистой победой — скалярное произведение. Там Balansis даёт 16 (предел float64) против нуля у numpy и у math.fsum. В сценарии суммирования победа делится: точный ответ дают и math.fsum, и встроенный sum(), и Balansis — разница только во времени, и она не в пользу Balansis (25×).

Цена компенсации

Скалярный слой — арифметика на объектах AbsoluteValue. Векторный — мост к numpy, через который проходят реальные пайплайны. Это две очень разные цены, и смешивать их в одну цифру «Balansis в N раз медленнее» было бы неправдой.

Скалярные операции, одна операция. Время — без накладных питоновского вызова (15.2 нс), они вычтены из обеих колонок. Измерено 2026-09-08
ОперацияЧто вызывается в Balansisfloat64BalansisЗамедление
сложение двух чиселOperations.compensated_add11.0 нс954.3 нс87×с накладными вызова: 37×
конструирование значенияAbsoluteValue.from_float (модель pydantic)7.8 нс884.4 нс114×с накладными вызова: 39×
two_sum (примитив EFT)two_sum — чистый float, без объектов10.8 нс64.2 нс5.9×с накладными вызова: 3.0×
two_product (примитив EFT)two_product — чистый float, без объектов10.8 нс131.6 нс12×с накладными вызова: 5.7×
Векторные операции на массивах по 100 000 элементов. Здесь накладные вызова на пять-шесть порядков меньше самой операции и на отношение не влияют. Измерено 2026-09-08
ОперацияЧто вызывается в Balansisfloat64BalansisЗамедление
поэлементное сложение массивов (n=100 000)compensated_array_add46.03 мкс927.20 мкс20×с накладными вызова: 20×
скалярное произведение (n=100 000)compensated_dot_product13.73 мкс11.28 мс821×с накладными вызова: 820×
softmax (n=100 000)compensated_softmax901.47 мкс21.19 мс24×с накладными вызова: 24×
Самая дорогая строка честнее всего описывает библиотеку. Компенсированное скалярное произведение на 100 000 элементах медленнее np.dot примерно в 820× — потому что BLAS считает в SIMD, а dot2 раскладывает каждое произведение на пару «значение + ошибка» и суммирует их точно. Это не недостаток реализации, это стоимость корректного округления. Разумный способ применения: считать быстро, а компенсированно — только там, где число обусловленности реально велико.

Где Balansis проигрывает

Собрано из тех же прогонов, что и таблицы выше. Измерено 2026-09-08
ЗадачаКто лучшеНасколькоПочему
Точная сумма списка floatmath.fsum25× по времени, равная точностьfsum реализован в C и даёт точную сумму; Balansis добавляет объекты и Python-цикл
Скалярное произведение хорошо обусловленных векторовnumpy.dot136× по временипри малом числе обусловленности ошибка BLAS и так на уровне последнего бита
SVDnumpy_gesdd (то есть сам numpy)105× по времени, ошибка восстановления хуже (5.395e-15 против 2.169e-15)бэкенд act_jacobi реализован на Python поверх dot2; в этом прогоне он не дал преимущества в точности
Деньги с копейкамиdecimal.Decimalточный ноль против ошибки 4.366e-13 на суммах вида 12345.67Ledger.post_entry приводит Decimal к float на входе
Softmaxnumpy со сдвигом на максимум24× по времени, точность та жеBalansis делает тот же сдвиг; отличие — суммирование знаменателя по Кэхэну

Повторить прогон

Для перепроверки не нужен ни доступ к нашей инфраструктуре, ни репозиторий сайта: сам сайт отдаёт скрипт-генератор и полученный им JSON. Нужны только Python и пакет с PyPI.

curl -O https://balansis.xteam.pro/repro/generate-benchmarks.py   # тот самый скрипт
curl -O https://balansis.xteam.pro/repro/benchmarks.json     # опубликованные числа

sha256sum generate-benchmarks.py
# 01439b8fd653e7c3a63b09fd86dbecba78c397454aca3f7475b305d90c0c915b

python -m venv .venv && . .venv/bin/activate
pip install "balansis==1.1.0" numpy
python generate-benchmarks.py --out mine.json

# сравнить: точность — должна совпасть, тайминги — нет
python - <<'EOF'
import json
mine, theirs = json.load(open("mine.json")), json.load(open("benchmarks.json"))
for a, b in zip(mine["scenarios"], theirs["scenarios"]):
    if a["id"] not in ("aggregation", "dot_product"):
        continue
    for x, y in zip(a["results"], b["results"]):
        print(a["id"], x["method"], x["value"] == y["value"], x["abs_error"], y["abs_error"])
EOF

Скрипт детерминирован по входным данным (все генераторы случайных чисел инициализированы фиксированным seed), поэтому расходиться должны только тайминги и, возможно, последние биты в BLAS-зависимых величинах — они зависят от сборки numpy. sha256 скрипта напечатан выше и продублирован в /repro/manifest.json вместе с суммами остальных файлов. Полный JSON — /repro/benchmarks.json, 22 КБ; аудит Lean4 — /repro/lean-audit.json и /repro/generate-lean-audit.py. Короткий вид суммы скрипта: 01439b8fd653e7c3