Бенчмарки
Точность против наивной арифметики — и цена этой точности
Всё на этой странице получено одним прогоном скрипта 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…, совпал).
Точность: сводка по сценариям
| Сценарий | Метод | Верных знаков | Абс. ошибка | Время (min) |
|---|---|---|---|---|
| Сумма 300 слагаемых со взаимным уничтожением | наивный цикл float64 | 0.0 | 100 | 3.11 мкс |
| встроенный sum() | 16 (предел float64) | 0 | 1.11 мкс | |
| numpy.sum (попарное суммирование) | 0.0 | 91 | 8.32 мкс | |
| math.fsum | 16 (предел float64) | 0 | 1.57 мкс | |
| Balansis Operations.sequence_sum | 16 (предел float64) | 0 | 39.31 мкс | |
| Скалярное произведение, cond ≈ 5e18 | наивный цикл float64 | 0.0 | 31.4694 | 37.81 мкс |
| numpy.dot (BLAS) | 0.0 | 31.4694 | 643.0 нс | |
| math.fsum над произведениями | 0.0 | 7.84441 | 51.89 мкс | |
| Balansis dot2 (Ogita–Rump–Oishi) | 16 (предел float64) | 4.478e-17 | 87.65 мкс | |
| Balansis numpy_integration.compensated_dot_product | 16 (предел float64) | 4.478e-17 | 87.57 мкс |
math.fsum. В сценарии суммирования победа делится: точный ответ дают и math.fsum, и встроенный sum(), и Balansis — разница только во времени, и она не в пользу Balansis (25×).Цена компенсации
Скалярный слой — арифметика на объектах AbsoluteValue. Векторный — мост к numpy, через который проходят реальные пайплайны. Это две очень разные цены, и смешивать их в одну цифру «Balansis в N раз медленнее» было бы неправдой.
| Операция | Что вызывается в Balansis | float64 | Balansis | Замедление |
|---|---|---|---|---|
| сложение двух чисел | Operations.compensated_add | 11.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× |
| Операция | Что вызывается в Balansis | float64 | Balansis | Замедление |
|---|---|---|---|---|
| поэлементное сложение массивов (n=100 000) | compensated_array_add | 46.03 мкс | 927.20 мкс | 20×с накладными вызова: 20× |
| скалярное произведение (n=100 000) | compensated_dot_product | 13.73 мкс | 11.28 мс | 821×с накладными вызова: 820× |
| softmax (n=100 000) | compensated_softmax | 901.47 мкс | 21.19 мс | 24×с накладными вызова: 24× |
np.dot примерно в 820× — потому что BLAS считает в SIMD, а dot2 раскладывает каждое произведение на пару «значение + ошибка» и суммирует их точно. Это не недостаток реализации, это стоимость корректного округления. Разумный способ применения: считать быстро, а компенсированно — только там, где число обусловленности реально велико.Где Balansis проигрывает
| Задача | Кто лучше | Насколько | Почему |
|---|---|---|---|
| Точная сумма списка float | math.fsum | 25× по времени, равная точность | fsum реализован в C и даёт точную сумму; Balansis добавляет объекты и Python-цикл |
| Скалярное произведение хорошо обусловленных векторов | numpy.dot | 136× по времени | при малом числе обусловленности ошибка BLAS и так на уровне последнего бита |
| SVD | numpy_gesdd (то есть сам numpy) | 105× по времени, ошибка восстановления хуже (5.395e-15 против 2.169e-15) | бэкенд act_jacobi реализован на Python поверх dot2; в этом прогоне он не дал преимущества в точности |
| Деньги с копейками | decimal.Decimal | точный ноль против ошибки 4.366e-13 на суммах вида 12345.67 | Ledger.post_entry приводит Decimal к float на входе |
| Softmax | numpy со сдвигом на максимум | 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…