Как стать автором
Обновить

Простой бенчмарк для PHP приложений

Время на прочтение5 мин
Количество просмотров4.8K

Очень часто приходится сравнивать время выполнения кода с целью выбора наиболее оптимального решения, и каждый раз для этого приходилось писать обработчик с расчётом времени обработки, после чего все результаты заносились в какую-нибудь таблицу 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 мс. Поэтому перед началом тестирования немного преобразуем данные в многомерный массив.

В качестве обработчика решил проверить следующую цепочку:

  1. очищаем от пробелов по обеим сторонам от букв;

  2. собираем в значении подряд 20 символов из буквы;

  3. преобразовываем полученное значение в верхний регистр;

  4. возвращаем результат выполнения.

В итоге, получаем такой код:

<?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 -
 ------- ----------------------- ------------------------ ------------------------

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 9: ↑8 и ↓1+9
Комментарии39

Публикации