/
cloud-castle
/
match
Обзор
Документация
Войти
/
cloud-castle
/
match
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
6
CI/CD
Аналитика
main
tools/bench-worker.php
228 строк
9 KB
Алексей Зорин
docs: привести сравнительные таблицы к пяти названным аналогам
27 июл 2026, 13:57
27 июл 2026, 13:57
b9042f9
Код
Авторство
О чём код?
<?php /** * Рабочий процесс замера: выполняет один сценарий для одной библиотеки и * печатает результат в JSON. * * Замеряется отдельным процессом, чтобы память и время одной библиотеки не * зависели от других. Память считается как прирост относительно базовой линии * ПОСЛЕ прогрева классов — так в результат попадает объём самих значений * библиотеки, а не всего процесса PHP. * * Использование: php bench-worker.php <библиотека> <сценарий> <итераций> */ declare(strict_types=1); require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/benchmark/vendor/autoload.php'; /** * Построить замыкание сценария для библиотеки. * * Возвращает `null`, если библиотека не поддерживает сценарий. * * @return null|callable(int): mixed */ function scenario(string $library, string $name): ?callable { $map = [ 'match' => [ 'sum' => static function (int $n): mixed { $value = CloudCastle\Match\BigDecimal::of('0.01'); $acc = CloudCastle\Match\BigDecimal::of('0.00'); for ($i = 0; $i < $n; ++$i) { $acc = $acc->plus($value); } return $acc; }, 'div' => static function (int $n): mixed { $one = CloudCastle\Match\BigDecimal::of('1'); $three = CloudCastle\Match\BigDecimal::of('3'); $acc = null; for ($i = 0; $i < $n; ++$i) { $acc = $one->dividedBy($three, 10); } return $acc; }, 'value' => static fn (int $i): mixed => CloudCastle\Match\BigDecimal::of((string) (100000 + $i) . '.456789'), ], 'brick/math' => [ 'sum' => static function (int $n): mixed { $value = Brick\Math\BigDecimal::of('0.01'); $acc = Brick\Math\BigDecimal::of('0.00'); for ($i = 0; $i < $n; ++$i) { $acc = $acc->plus($value); } return $acc; }, 'div' => static function (int $n): mixed { $one = Brick\Math\BigDecimal::of('1'); $three = Brick\Math\BigDecimal::of('3'); $acc = null; for ($i = 0; $i < $n; ++$i) { $acc = $one->dividedBy($three, 10, Brick\Math\RoundingMode::HALF_UP); } return $acc; }, 'value' => static fn (int $i): mixed => Brick\Math\BigDecimal::of((string) (100000 + $i) . '.456789'), ], 'litipk/php-bignumbers' => [ 'sum' => static function (int $n): mixed { $value = Litipk\BigNumbers\Decimal::fromString('0.01'); $acc = Litipk\BigNumbers\Decimal::fromString('0.00'); for ($i = 0; $i < $n; ++$i) { $acc = $acc->add($value); } return $acc; }, 'div' => static function (int $n): mixed { $one = Litipk\BigNumbers\Decimal::fromString('1'); $three = Litipk\BigNumbers\Decimal::fromString('3'); $acc = null; for ($i = 0; $i < $n; ++$i) { $acc = $one->div($three, 10); } return $acc; }, 'value' => static fn (int $i): mixed => Litipk\BigNumbers\Decimal::fromString((string) (100000 + $i) . '.456789'), ], 'moontoast/math' => [ 'sum' => static function (int $n): mixed { $acc = new Moontoast\Math\BigNumber('0.00', 2); for ($i = 0; $i < $n; ++$i) { // BigNumber изменяем: add() правит значение на месте. Чтобы // работа была эквивалентна неизменяемым библиотекам (каждая // операция даёт независимое значение), результат копируется. $acc = (clone $acc)->add('0.01'); } return $acc; }, 'div' => static function (int $n): mixed { $acc = null; for ($i = 0; $i < $n; ++$i) { $acc = (new Moontoast\Math\BigNumber('1', 10))->divide('3'); } return $acc; }, 'value' => static fn (int $i): mixed => new Moontoast\Math\BigNumber((string) (100000 + $i) . '.456789', 6), ], // math-php считает научные формулы на float: точность ограничена ~15 // знаками, поэтому библиотека идёт вне зачёта по точности. 'markrogoyski/math-php (float)' => [ 'sum' => static function (int $n): mixed { $acc = 0.0; for ($i = 0; $i < $n; ++$i) { $acc += 0.01; } return $acc; }, 'div' => static function (int $n): mixed { $acc = 0.0; for ($i = 0; $i < $n; ++$i) { $acc = 1 / 3; } return $acc; }, 'value' => static fn (int $i): mixed => 100000.456789 + $i, ], // markbaker/complex — комплексная арифметика на float: та же // ограниченная точность, объектная модель как у точных библиотек. 'markbaker/complex (float)' => [ 'sum' => static function (int $n): mixed { $value = new Complex\Complex(0.01); $acc = new Complex\Complex(0.0); for ($i = 0; $i < $n; ++$i) { $acc = $acc->add($value); } return $acc; }, 'div' => static function (int $n): mixed { $one = new Complex\Complex(1.0); $three = new Complex\Complex(3.0); $acc = null; for ($i = 0; $i < $n; ++$i) { $acc = $one->divideBy($three); } return $acc; }, 'value' => static fn (int $i): mixed => new Complex\Complex(100000.456789 + $i), ], ]; return $map[$library][$name] ?? null; } $library = $argv[1] ?? ''; $name = $argv[2] ?? ''; $iterations = (int) ($argv[3] ?? 1000); $callable = scenario($library, $name); if ($callable === null) { echo json_encode(['supported' => false], JSON_THROW_ON_ERROR); exit(0); } if ($name === 'value') { // Замер памяти: прогрев, затем удержание N значений и разница памяти. $callable(0); // Хранилище выделяется ДО базовой линии и не растёт по ходу замера, // поэтому в прирост памяти попадают только сами значения библиотеки. $held = new SplFixedArray($iterations); gc_collect_cycles(); $before = memory_get_usage(); for ($i = 0; $i < $iterations; ++$i) { $held[$i] = $callable($i); } gc_collect_cycles(); $after = memory_get_usage(); // Прирост памяти на одно удержанное значение. Накладные расходы массива // одинаковы для всех библиотек, поэтому сравнение остаётся честным. echo json_encode([ 'supported' => true, 'bytes_per_value' => round(($after - $before) / max(1, count($held)), 1), 'count' => count($held), ], JSON_THROW_ON_ERROR); exit(0); } // Прогрев классов и опкода, чтобы в замер не попала первичная загрузка. $callable(1); gc_collect_cycles(); $start = hrtime(true); $callable($iterations); $elapsed = (hrtime(true) - $start) / 1e6; echo json_encode([ 'supported' => true, 'milliseconds' => round($elapsed, 3), 'ops_per_second' => $elapsed > 0.0 ? (int) round($iterations / ($elapsed / 1000)) : 0, ], JSON_THROW_ON_ERROR);