Очень часто приходится сравнивать время выполнения кода с целью выбора наиболее оптимального решения, и каждый раз для этого приходилось писать обработчик с расчётом времени обработки, после чего все результаты заносились в какую-нибудь таблицу Excel и вручную рассчитывалось минимальное, максимальное и среднее время выполнения...
Для решения этой тривиальной задачи был выпущен пакет "Benchmark", позволяющий сравнивать время выполнения кода без лишних затрат.
Установка
Проще всего установить пакет при помощи пакетного менеджера Composer:
composer require dragon-code/benchmark --dev
И всё, пакет готов к работе.
Использование
Так как вся нужная информация выводится в консоль, то лучше вызывать данный код через консоль хоть через команды используемого Вами фреймворка, хоть напрямую через вызов файла, например, php handler.php
.
use DragonCode\Benchmark\Benchmark;
(new Benchmark())->compare(
fn () => /* some code */,
fn () => /* some code */,
);
(new Benchmark())->compare([
fn () => /* some code */,
fn () => /* some code */,
]);
(new Benchmark())->compare([
'foo' => fn () => /* some code */,
'bar' => fn () => /* some code */,
]);
Передавать можно неограниченное количество аргументов.
Все переданные колбэки будут вызываться строго в указанном порядке. Если Вы хотите явно определить имена, то можете в метод compare
передать ассоциативный массив, где в качестве ключа будет указано название проверяемого функционала.
Пример результата выполнения
------- --------------------- --------------------
# 0 1
------- --------------------- --------------------
1 0.0101 ms - 64.8Kb 0.0014 ms - 13.7Kb
2 0.0099 ms - 59.5Kb 0.0011 ms - 12.9Kb
3 0.0014 ms - 59.1Kb 0.001 ms - 12.5Kb
4 0.0012 ms - 58.8Kb 0.0011 ms - 12.2Kb
5 0.0012 ms - 58.4Kb 0.0011 ms - 11.8Kb
6 0.0011 ms - 58Kb 0.001 ms - 11.4Kb
7 0.0011 ms - 57.6Kb 0.001 ms - 11Kb
8 0.0011 ms - 57.3Kb 0.001 ms - 10.7Kb
9 0.0012 ms - 56.6Kb 0.001 ms - 10Kb
...
100 0.0011 ms - 14.8Kb 0.0011 ms - 4.1Kb
------- --------------------- --------------------
min 0.001 ms - 14.8Kb 0.001 ms - 4.1Kb
max 0.0101 ms - 64.8Kb 0.0026 ms - 13.7Kb
avg 0.00122 ms - 47.5Kb 0.0016 ms - 4.1Kb
total 0.7998 ms - 64.8Kb 0.6156 ms - 50.9Kb
------- --------------------- --------------------
Order - 1 - - 2 -
------- --------------------- --------------------
Количество итераций
По-умолчанию каждый колбэк проходит 10 итераций, но Вы можете задать своё количество вызывав метод iterations
:
use DragonCode\Benchmark\Benchmark;
(new Benchmark())
->iterations(5)
->compare(
fn () => /* some code */,
fn () => /* some code */,
);
В случае если переданное значение будет меньше единицы, то скрипт будет использовать значение "1".
Пример результата выполнения
------- --------------------- ---------------------
# 0 1
------- --------------------- ---------------------
1 0.0077 ms - 64.8Kb 0.0015 ms - 57.3Kb
2 0.0023 ms - 59.5Kb 0.0013 ms - 56.5Kb
3 0.0013 ms - 59.1Kb 0.0012 ms - 56.1Kb
4 0.0012 ms - 58.8Kb 0.0011 ms - 55.8Kb
5 0.0011 ms - 58.4Kb 0.0011 ms - 55.4Kb
------- --------------------- ---------------------
min 0.0011 ms - 58.4Kb 0.0011 ms - 55.4Kb
max 0.0077 ms - 64.8Kb 0.0015 ms - 57.3Kb
avg 0.00272 ms - 60.1Kb 0.00124 ms - 56.2Kb
total 0.2453 ms - 64.8Kb 0.1105 ms - 57.3Kb
------- --------------------- ---------------------
Order - 2 - - 1 -
------- --------------------- ---------------------
Округление значений
По-умолчанию скрипт не округляет результаты замеров, но Вы можете указать количество символов после запятой, до которых может производиться округление:
use DragonCode\Benchmark\Benchmark;
(new Benchmark())
->roundPrecision(2)
->compare(
fn () => /* some code */,
fn () => /* some code */,
);
Пример результата выполнения
------- ------------------- -------------------
# 0 1
------- ------------------- -------------------
1 11 ms - 64.8Kb 14.93 ms - 57.3Kb
2 14.58 ms - 59.5Kb 15.58 ms - 56.5Kb
3 15.62 ms - 59.1Kb 14.97 ms - 56.1Kb
4 15.73 ms - 58.8Kb 16.15 ms - 55.8Kb
5 14.25 ms - 58.4Kb 15.55 ms - 55.4Kb
------- ------------------- -------------------
min 11 ms - 58.4Kb 14.93 ms - 55.4Kb
max 15.73 ms - 64.8Kb 16.15 ms - 57.3Kb
avg 14.24 ms - 60.1Kb 15.44 ms - 56.2Kb
total 72.03 ms - 64.8Kb 77.78 ms - 57.3Kb
------- ------------------- -------------------
Order - 1 - - 2 -
------- ------------------- -------------------
Вывод только итоговой информации
В случае если Вы проверяете скорость выполнения при большом количестве итераций или просто не хотите смотреть на подробную статистику времени выполнения по каждой итерации, Вы можете вызвать метод withoutData
, который позволит скрыть её, отобразив в конечном итоге только суммарную информацию:
use DragonCode\Benchmark\Benchmark;
(new Benchmark())
->withoutData()
->compare([
'foo' => fn () => /* some code */,
'bar' => fn () => /* some code */,
]);
Пример результата выполнения
------- ------------------- -------------------
# foo bar
------- ------------------- -------------------
min 12.02 ms - 58.4Kb 14.71 ms - 55.4Kb
max 15.66 ms - 64.8Kb 15.67 ms - 57.3Kb
avg 14.65 ms - 60.1Kb 15.17 ms - 56.2Kb
total 73.93 ms - 64.8Kb 76.31 ms - 57.3Kb
------- ------------------- -------------------
Order - 1 - - 2 -
------- ------------------- -------------------
Расчёт победителей
Порядок определяется по среднему арифметическому значению и обозначается числом от 1 и выше, где "1" - это наименьшее затраченное время.
Заключение
Теперь сравнивать время выполнения кода стало значительно проще и быстрее, и можно не тратить время на реализацию данного функционала ?
Пример кода и результат выполнения частого вопроса среди начинающих
Так как для тестирования выбрал простой массив, то все изменения происходят настолько быстро, что код всегда показывает на выходе время выполнения 0.000 мс. Поэтому перед началом тестирования немного преобразуем данные в многомерный массив.
В качестве обработчика решил проверить следующую цепочку:
очищаем от пробелов по обеим сторонам от букв;
собираем в значении подряд 20 символов из буквы;
преобразовываем полученное значение в верхний регистр;
возвращаем результат выполнения.
В итоге, получаем такой код:
<?php
declare(strict_types=1);
use DragonCode\Benchmark\Benchmark;
require 'vendor/autoload.php';
class Test
{
protected array $values = [
' a ',
' b ',
' c ',
' d ',
' e ',
' f ',
' g ',
' h ',
' i ',
' j ',
' k ',
' l ',
' m ',
' n ',
' o ',
' p ',
' q ',
' r ',
' s ',
' t ',
' u ',
' v ',
' w ',
' x ',
' y ',
' z ',
];
public function __construct(
protected Benchmark $benchmark = new Benchmark()
) {
}
public function compare(int $iterations): void
{
$data = $this->prepareData();
$this->benchmark
->withoutData()
->iterations($iterations)
->compare([
'foreach' => fn () => $this->each($data),
'array_map' => fn () => $this->map($data),
'array_walk' => fn () => $this->walk($data),
]);
}
protected function prepareData(): array
{
$result = [];
foreach ($this->values as $value) {
$result[$value] = $this->values;
}
return $result;
}
protected function each(array $values): array
{
foreach ($values as &$value) {
if (is_array($value)) {
$value = $this->each($value);
continue;
}
$value = $this->change($value);
}
return $values;
}
protected function map(array $values): array
{
return array_map(
fn ($value) => is_array($value)
? $this->map($value)
: $this->change($value),
$values
);
}
protected function walk(array $values): array
{
array_walk($values, fn ($value) => is_array($value)
? $this->walk($value)
: $this->change($value)
);
return $values;
}
protected function change(string $value): string
{
$value = trim($value);
$value = str_pad('', 20, $value);
return mb_strtoupper($value);
}
}
(new Test())->compare(1000);
И результат его выполнения при двух запусках:
------- ----------------------- ------------------------ ------------------------
# foreach array_map array_walk
------- ----------------------- ------------------------ ------------------------
min 0.8488 ms - 91.3Kb 1.074 ms - 69.5Kb 1.1541 ms - 8.2Kb
max 2.0596 ms - 91.3Kb 2.4338 ms - 90.2Kb 1.8704 ms - 68.8Kb
avg 0.939462 ms - 91.3Kb 1.236546 ms - 70.1Kb 1.269086 ms - 11.4Kb
total 929.3684 ms - 508.4Kb 1227.5821 ms - 482.1Kb 1280.3196 ms - 420.7Kb
------- ----------------------- ------------------------ ------------------------
Order - 1 - - 2 - - 3 -
------- ----------------------- ------------------------ ------------------------