/
cloud-castle
/
match
Обзор
Документация
Войти
/
cloud-castle
/
match
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
6
CI/CD
Аналитика
main
tools/bench.php
125 строк
5 KB
Алексей Зорин
docs: привести сравнительные таблицы к пяти названным аналогам
27 июл 2026, 13:57
27 июл 2026, 13:57
b9042f9
Код
Авторство
О чём код?
<?php /** * Оркестратор сравнительных замеров: гоняет сценарии по всем библиотекам * в отдельных процессах и складывает результат в `var/bench/results.json`. * * Каждый замер выполняется в чистом процессе без Xdebug — так время и память * одной библиотеки не зависят от других и от отладочного расширения. * * Использование: composer bench */ declare(strict_types=1); const LIBRARIES = [ 'match', 'brick/math', 'litipk/php-bignumbers', 'moontoast/math', 'markrogoyski/math-php (float)', 'markbaker/complex (float)', ]; /** * Библиотеки вне зачёта: считают на float приближённо (~15 знаков) и приводятся * для масштаба, а не как соперники по точности. */ const REFERENCES = ['markrogoyski/math-php (float)', 'markbaker/complex (float)']; const SCENARIOS = [ 'sum' => ['iterations' => 20000, 'title' => 'Сложение (20 000 операций)', 'unit' => 'ops/s', 'higher' => true], 'div' => ['iterations' => 5000, 'title' => 'Деление с масштабом 10 (5 000)', 'unit' => 'ops/s', 'higher' => true], // Выборка 50 000: при меньшей размер блоков аллокатора маскирует разницу. 'value' => ['iterations' => 50000, 'title' => 'Память на одно значение', 'unit' => 'байт', 'higher' => false], ]; /** * Выполнить один замер в отдельном процессе. * * @return array<string, mixed> результат замера */ function measure(string $library, string $scenario, int $iterations): array { $command = sprintf( 'XDEBUG_MODE=off %s %s %s %s %d 2>/dev/null', escapeshellarg(PHP_BINARY), escapeshellarg(__DIR__ . '/bench-worker.php'), escapeshellarg($library), escapeshellarg($scenario), $iterations, ); $output = shell_exec($command); if (!is_string($output) || trim($output) === '') { return ['supported' => false]; } try { /** @var array<string, mixed> $decoded */ $decoded = json_decode(trim($output), true, 512, JSON_THROW_ON_ERROR); return $decoded; } catch (JsonException) { return ['supported' => false]; } } $results = [ 'generated_at' => date('c'), 'php_version' => PHP_VERSION, 'scenarios' => [], ]; foreach (SCENARIOS as $scenario => $meta) { $row = ['title' => $meta['title'], 'unit' => $meta['unit'], 'higher_is_better' => $meta['higher'], 'values' => []]; foreach (LIBRARIES as $library) { // Три прогона, берём лучший — сглаживает шум планировщика. $best = null; for ($attempt = 0; $attempt < 3; ++$attempt) { $measured = measure($library, $scenario, $meta['iterations']); if (($measured['supported'] ?? false) !== true) { continue; } $value = $scenario === 'value' ? (float) $measured['bytes_per_value'] : (float) $measured['ops_per_second']; if ($best === null || ($meta['higher'] ? $value > $best : $value < $best)) { $best = $value; } } $row['values'][$library] = $best; printf("%-24s %-8s %s\n", $library, $scenario, $best === null ? 'н/д' : (string) round($best, 1)); } // Победитель определяется среди ТОЧНЫХ библиотек: float считает приближённо // (~15 знаков) и приведён как нижняя граница стоимости, а не как соперник. $contenders = array_filter( $row['values'], static fn (?float $value, string $library): bool => $value !== null && !in_array($library, REFERENCES, true), ARRAY_FILTER_USE_BOTH, ); $row['winner'] = $contenders === [] ? null : array_search($meta['higher'] ? max($contenders) : min($contenders), $row['values'], true); $results['scenarios'][$scenario] = $row; } $directory = __DIR__ . '/../var/bench'; if (!is_dir($directory)) { mkdir($directory, 0o777, true); } file_put_contents( $directory . '/results.json', json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), ); echo "\nРезультаты: var/bench/results.json\n";