Очень часто приходится сравнивать время выполнения кода с целью выбора наиболее оптимального решения, и каждый раз для этого приходилось писать обработчик с расчётом времени обработки, после чего все результаты заносились в какую-нибудь таблицу 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       12.5914 ms    15.5279 ms   
  2       15.2767 ms    15.5117 ms   
  3       15.5172 ms    15.3314 ms   
  4       14.6393 ms    15.3283 ms   
  5       15.278 ms     15.5241 ms   
  6       16.1444 ms    15.1441 ms   
  7       15.3792 ms    14.2409 ms   
  8       15.2188 ms    14.6961 ms   
  9       15.1009 ms    15.71 ms     
  10      14.9955 ms    15.522 ms    
 ------- ------------- ------------- 
  min     12.5914 ms    14.2409 ms   
  max     16.1444 ms    15.71 ms     
  avg     15.01414 ms   15.25365 ms  
 ------- ------------- ------------- 
  Order   - 1 -         - 2 -        
 ------- ------------- -------------

Количество итераций

По-умолчанию каждый колбэк проходит 10 итераций, но Вы можете задать своё количество вызывав метод iterations:

use DragonCode\Benchmark\Benchmark;

(new Benchmark())
    ->iterations(5)
    ->compare(
        fn () => /* some code */,
        fn () => /* some code */,
    );

В случае если переданное значение будет меньше единицы, то скрипт будет использовать значение "1".

Пример результата выполнения
 ------- ------------- ------------- 
  #       0             1            
 ------- ------------- ------------- 
  1       11.6046 ms    15.89 ms     
  2       14.8923 ms    15.6117 ms   
  3       15.5306 ms    15.3212 ms   
  4       14.8029 ms    14.7584 ms   
  5       14.7727 ms    14.9909 ms   
 ------- ------------- ------------- 
  min     11.6046 ms    14.7584 ms   
  max     15.5306 ms    15.89 ms     
  avg     14.32062 ms   15.31444 ms  
 ------- ------------- ------------- 
  Order   - 1 -         - 2 -        
 ------- ------------- ------------- 

Округление значений

По-умолчанию скрипт не округляет результаты замеров, но Вы можете указать количество символов после запятой, до которых может производиться округление:

use DragonCode\Benchmark\Benchmark;

(new Benchmark())
    ->roundPrecision(2)
    ->compare(
        fn () => /* some code */,
        fn () => /* some code */,
    );
Пример результата выполнения
 ------- ---------- ---------- 
  #       0          1         
 ------- ---------- ---------- 
  1       11.1 ms    15.19 ms  
  2       14.65 ms   15.22 ms  
  3       15.3 ms    15.3 ms   
  4       15.1 ms    15.55 ms  
  5       14.91 ms   15.75 ms  
 ------- ---------- ---------- 
  min     11.1 ms    15.19 ms  
  max     15.3 ms    15.75 ms  
  avg     14.21 ms   15.4 ms   
 ------- ---------- ---------- 
  Order   - 1 -      - 2 -     
 ------- ---------- ---------- 

Вывод только итоговой информации

В случае если Вы проверяете скорость выполнения при большом количестве итераций или просто не хотите смотреть на подробную статистику времени выполнения по каждой итерации, Вы можете вызвать метод withoutData, который позволит скрыть её, отобразив в конечном итоге только суммарную информацию:

use DragonCode\Benchmark\Benchmark;

(new Benchmark())
    ->withoutData()
    ->compare([
        'foo' => fn () => /* some code */,
        'bar' => fn () => /* some code */,
    ]);
Пример результата выполнения
 ------- ------------- ------------- 
  #       foo          bar            
 ------- ------------- ------------- 
  min     11.5861 ms    14.6096 ms   
  max     15.9592 ms    15.8362 ms   
  avg     14.65038 ms   15.08028 ms  
 ------- ------------- ------------- 
  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.8363 ms      1.0899 ms      1.143 ms
  max     3.2254 ms      5.1938 ms      2.1181 ms
  avg     0.8964671 ms   1.1901751 ms   1.1914344 ms
 ------- -------------- -------------- --------------
  Order   - 1 -          - 2 -          - 3 -
 ------- -------------- -------------- --------------

 ------- -------------- -------------- --------------
  #       foreach        array_map      array_walk
 ------- -------------- -------------- --------------
  min     0.8358 ms      1.09 ms        1.1336 ms
  max     4.2263 ms      5.2699 ms      5.1957 ms
  avg     0.8951935 ms   1.1673948 ms   1.2270636 ms
 ------- -------------- -------------- --------------
  Order   - 1 -          - 2 -          - 3 -
 ------- -------------- -------------- --------------

PS: И всё же, это функционал бенчмарка, поэтому переименовал проект.

Комментарии (32)


  1. Dekmabot
    04.02.2023 01:44
    +1

    Простой и полезный скрипт, спасибо, часто провожу подобные тесты на коленке, взял на вооружение.

    Столкнулся с округлением на маленьких значениях, попробую предложить pr, возможно с show($precision) , округляя уже на этапе вывода статистики.

        $range = [0, 1000000]; // 1M
    
        (new Comparator())
            ->iterations(100000) // 100K
            ->withoutData()
            ->compare([
                'rand' => fn() => rand(...$range),
                'mt_rand' => fn() => mt_rand(...$range),
                'random_int' => fn() => random_int(...$range),
            ]);


    1. Helldar Автор
      04.02.2023 02:39
      +1

      Да, я тоже понял это и добавил возможность вручную задавать округление. Статью обновил. Спасибо за отзыв :)

      $range = [0, 1000000];
      
      (new Comparator())
          ->iterations(100000)
          ->withoutData()
          ->compare([
              'rand'       => fn () => rand(...$range),
              'mt_rand'    => fn () => mt_rand(...$range),
              'random_int' => fn () => random_int(...$range),
          ]);
       ----- --------------------- -------------------- --------------------
        #     rand                  mt_rand              random_int
       ----- --------------------- -------------------- --------------------
        min   0                     0                    0
        max   0.00011301040649414   3.8862228393555E-5   3.6001205444336E-5
        avg   9.6567630767822E-7    9.7702264785767E-7   1.0187935829163E-6
       ----- --------------------- -------------------- --------------------
              winner                loser                loser
       ----- --------------------- -------------------- --------------------


  1. a-tk
    04.02.2023 09:13

    А как там с мультимодальными распределениями дела?


    1. Helldar Автор
      04.02.2023 12:46

      Пакету не важно что отправляется в колбэк. По сути, весь код это обёртка над:

      $startAt = microtime(true);
      
      /* user function */
      $callback();
      
      return microtime(true) - $startAt;


      1. a-tk
        04.02.2023 13:42
        +1

        То есть бесполезно.

        Вообще прежде чем делать нечто с закосом на бенчмаркинг, попробуйте сначала ознакомиться в общих чертах с проблемами в процессе бенчмаркинга.


  1. FanatPHP
    04.02.2023 10:42

    Долго корпели, считали, и получили "виннера" с разницей меньше погрешности измерения.
    А потом и рождаются все эти адские байки про "одинарные ковычки быстрее"


    и можно не тратить время на реализацию данного функционала

    Да-да, и потратить его на бессмысленные измерения.


    1. Helldar Автор
      04.02.2023 12:49
      +1

      Разница одинарных и двойных кавычек в том, что одинарные принимаются интерпретатором "как есть", а двойные парсятся внутренним компилятором с целью обнаружения в них переменных для подстановки значений.

      Так что, зная это, можно и без всяких проверок уверенно сказать, что если текст не содержит обращений к переменным внутри себя, то нужно использовать одинарные кавычки.


      1. FanatPHP
        04.02.2023 13:54
        +2

        Странно, что вы подхватили этот, в сущности, второстепенный вопрос с кавычками. Речь не о нем. Но, с другой стороны, это как раз очень характерно для таких увлеченных оптимизаторов, которым надо "здесь и сейчас", без всякой связи с реальностью и без попытки подумать хотя бы на один ход вперёд. И в этом смысле кавычки очень показательны, да.


        Логично предположить, что если нас интересует производительность РНР кода, то первым шагом, который будет сделан — это включение опкод кэша. В котором, как можно увидеть, уже и близко не остается никакой разницы между кавычками. То есть, если нас интересует реальная реальная производительность, а не воображаемая, то она достигается не разницей между кавычками.


        И то же самое относится и ко всем другим случаям оптимизации. Если код работает медленно, надо не кавычки переставлять, а подход менять. Сокращать объем обрабатываемых данных, распараллеливать задачу, оптимизировать алгоритм.


        А все это крохоборство на синтаксисе — это самообман, и пустая трата ресурсов.


        1. Helldar Автор
          04.02.2023 14:32
          +1

          ...без всякой связи с реальностью и без попытки подумать хотя бы на один ход вперёд. И в этом смысле кавычки очень показательны, да.

          Согласен, показательны. Показательны в том, что, во-первых, не нужно использовать "расширенную" механику там где она не оправдана, а также в том, что предварительная оптимизация является плохим решением.

          (new Comparator())
              ->withoutData()
              ->iterations(100000)
              ->compare([
                  'single' => fn () => 'foo' . $value,
                  'double' => fn () => "foo$value",
              ]);
          Результаты выполнения
           ----- -------------------- ---------------------
            #     single               double
           ----- -------------------- ---------------------
            min   0                    0
            max   5.3167343139648E-5   0.00011587142944336
            avg   8.6287260055542E-7   8.7460517883301E-7
           ----- -------------------- ---------------------
                  winner               loser
           ----- -------------------- ---------------------
           ----- -------------------- --------------------
            #     single               double
           ----- -------------------- --------------------
            min   0                    0
            max   2.1934509277344E-5   2.598762512207E-5
            avg   8.3098649978638E-7   8.3781242370605E-7
           ----- -------------------- --------------------
                  winner               loser
           ----- -------------------- --------------------
          
           ----- -------------------- --------------------
            #     single               double
           ----- -------------------- --------------------
            min   0                    0
            max   2.7894973754883E-5   4.1961669921875E-5
            avg   8.3756923675537E-7   8.5575819015503E-7
           ----- -------------------- --------------------
                  winner               loser
           ----- -------------------- --------------------
          

          Если код работает медленно, надо не кавычки переставлять, а подход менять.

          Именно для этого и нужен этот пакет - понять какой подход будет работать быстрее.

          А все это крохоборство на синтаксисе — это самообман, и пустая трата ресурсов.

          Понял, Ваш тимлид не даёт пачкать код и Вы таким образом пытаетесь вылить негатив на меня. Ок, учту.


          1. FanatPHP
            04.02.2023 14:53
            +3

            Но вы-то в ваших тестах меняете не подход, а синтаксис. При том что для радикальных изменений какие-то особые измерения не нужны — при нормальной оптимизации все будет видно невооруженным взглядом. Скажем, вместо того, чтобы читать значение из гигабайтного джейсон файла, использовать хранилище с произвольным доступом. А ради разницы в 0.2 миллисекунды и затеваться не стоило.


            Понял, Ваш тимлид не даёт пачкать код

            На будущее, делать предположения о событиях, не относящихся к обсуждаемому вопросу — это не очень хорошая практика, и не очень одобряется сообществом.


            1. Helldar Автор
              04.02.2023 15:06

              Ради 0.2 мс разумеется не стоит. Это экономия на спичках получится. В этом случае лучше выбрать путь, позволяющий легче реализовывать задачу.

              На днях у меня был кейс где нужно было проверить общую скорость работы: найти в базе запись, получить значение из одной колонки, рассчитать процент по входящему числу и положить в другую колонку той же записи.

              Сам кейс заключался в проверки двух подходов: на 1000 записей выполнить 1 джобу, которая одним запросом получит из базы все значения, произведёт вычисления и вторым запросом массово положит обратно, или же выполнить 1000 отдельных джоб, выполняющих одну операцию.

              Сам тест показал что 1 джоба технически сработает быстрее, но был и второй момент - скорость обновления записей. Пока одна джоба перебирает цикл, в базе уже могли измениться значения и это означает, что 1 джоба, по сути, сломает её.

              Тем не менее, вопрос был не в целостности и актуальности данных, а именно в скорости работы двух подходов.

              ...не относящихся к обсуждаемому вопросу...

              Обратите внимание, что данный вопрос как раз-таки обсуждался и начали это именно Вы.


              1. FanatPHP
                04.02.2023 15:16
                +1

                Ну то есть вам понадобился тест, чтобы выяснить, что один сделать запрос будет быстрее, чем последовательно выполнить 1000. Понятно.


                1. Helldar Автор
                  04.02.2023 15:37
                  +1

                  На практике далеко не всегда 1 запрос будет работать быстрее 1000 отдельных. Так что да, для проверки потребовалось тестирование.


              1. ysoft
                04.02.2023 16:28

                да, но что мешало сделать shared lock в транзакции? также можно применить и атомарные операции, да много чего можно придумать не используя 1000 апдейтов


          1. FanatPHP
            04.02.2023 18:22
            +2

            О, да вы обновили комментарий, добавив в него пример, на котором я как раз хотел остановиться, но не было подходящего кейса. Спасибо, это ровно то, чего здесь не хватало!


            Я не буду даже к вам приставать с той гигантской разницей в цифрах, которую вы получили на этих тестах. И рассказывать, что получил сходные результаты… оставив в обеих ветках идентичный код с одинарными кавычками.


            На самом деле главной проблемой всех этих тестов является даже не их бессмысленность, а то, что тестирующий зачастую не понимает — а что он, собственно, тестирует-то. Но ничтоже сумняшеся делает далеко идущие выводы. Продумать эксперимент и правильно интерпретировать его результаты — это целая наука. Про это даже хороший анекдот есть:


            Один исследователь решил узнать, где у таракана уши.
            Поймал таракана, посадил на стол, постучал по столешнице — таракан убежал.
            Поймал снова, оторвал ноги, постучал — таракан никуда не бежит. Значит, не слышит.
            Вывод — у таракана уши в ногах!

            Вот и сейчас.
            Вы зачем-то добавили в свой комментарий тест "конкатенация vs. интерполяция". Но ведь хотели-то — "single vs. double", верно?


            Здесь у вас получилось как минимум две методологические ошибки:


            • во-первых, вы вообще никак не тестируете парсинг строк. Парсится этот ваш код "внутренним компилятором" ровно один раз, при старте. А когда запускается цикл на офигиллиард итераций, то оба строковых литерала — это уже просто адреса в памяти, без всяких кавычек. И внутри цикла никакого парсинга не происходит. Чтобы протестировать разницу в парсинге, вам надо запускать в цикле именно парсинг. Инклюдом например (убедившись в том, что опкод кэш отключен). Но в этом случае у вас погрешность будет вносить дисковый ввод-вывод.
            • во-вторых, как я написал выше, вся разница в вашем коде заключается в способе объединения двух строк, конкатенация vs. интерполяция. Совершенно лишняя операция, которую вы зачем-то добавили. Если бы кавычки в этом тесте парсились, то добавление лишней операции исказило бы результаты. Но поскольку единственным отличием двух тестов является способ соединения строк, то в итоге вы тестируете вообще не то, что хотели

            И это мы еще даже не касались размеров или количества операндов. А на результаты теста может влиять что угодно — и состав операндов стоит одним из первых в очереди и смотрит на вас грустными глазами Шлёмы-маляра. И стоит нам поменять тестовую строку с "Hallo $world"; на "Hi! My name is $name and I am $age years old! I love doing $hobby!"; как виннеры и лузеры внезапно меняются местами! И что теперь делать с этими результатами? Продолжать рассказывать всем, что одинарные быстрее?


            Вот это-то и является главной проблемой. Берется какой-нибудь вырожденный случай, который в лучшем случае имеет весьма отдаленное отношение к исследуемой проблеме, а обычно — так и вообще никакого. И потом на основании полученных результатов переписываются гайдлайны, рефакторится значительное количество кода, распространяются нелепые слухи. А всего этого вполне можно было бы избежать, если не упарываться в микротесты, а заниматься оптимизацией профессионально — отталкиваясь от результатов профайлинга...


            1. Helldar Автор
              04.02.2023 19:33
              +2

              В целом, согласен. Тестирование тоже своего рода искусство.


  1. tzlom
    04.02.2023 12:04
    +1

    Идея лучше реализации. Нужно считать стандартное отклонение и сравнивать уже учитывая его. Так же имеет смысл указывать место на пьедестале а не победитель и остальные.

    ознакомьтесь с google benchmark - идея та же но реализацию они отточили чтобы этим цифрам можно было верить


    1. Helldar Автор
      04.02.2023 13:00

      Ну как сказать лучше... Так или иначе самый наипростейший способ отслеживать время выполнения кода - использовать функцию microtime сравнивая значения до и после. Пример писал в комментарии, поэтому дублировать не стоит. И именно "стандартное отклонение", читай "среднее арифметическое" и берётся как основное значение для сравнения, т.к. минимальная и максимальная пиковая нагрузка не всегда может показывать реальные показатели по каким-либо причинам.

      Что касается цифрового обозначения порядка, согласен. Возможно на досуге реализую.

      По поводу Google Benchmark, он под C++ и у него свои особенности реализации. В случае с PHP "бенчмарк" совсем по-другому работает, но общий принцип тот же - замер времени между началом и завершением выполнения. И эта цель достигнута в этом программном продукте.


      1. a-tk
        04.02.2023 13:45

        Бенчмарк - это не про прогнать несколько раз и посчитать, это ещё и про оценку того, насколько статистике можно верить, на основе статистики.
        Вы ведь не станете утверждать, что распределение времени всегда строго нормальное?


        1. Dekmabot
          04.02.2023 14:08
          +1

          Это решение не про бенчмарки, на сколько я понял автора, а про "сравнить мои решения между собой" по времени на глазок и выявить явного лидера. Даже "winner/loser" как бы намекают на несерьёзность :)

          Если бы мне нужно было сравнение в изолированной среде с нормальным распределением, замерами памяти, сети и io, я бы выбрал другое решение, и потратил бы на него больше времени.


          1. Helldar Автор
            04.02.2023 14:36

            Аналогично. Если нужно что-то более мощное, то явно воспользовался другими инструментами тестирования. Да хоть тот же Яндекс.Танк для нагрузочного тестирования.

            И да, Вы правы, текущий пакет не претендует на место среди проверочных инструментов. Он, скорее, "здесь и сейчас на глаз сравнить решения между собой", как Вы и сказали.


        1. Helldar Автор
          04.02.2023 14:35

          Нет, не стану, и именно поэтому основной единицей сравнения является среднее арифметическое из всех значений, т.к. оно ближе к правде, но не всегда соответствует. И это понимает любой разработчик.

          И бенчмарк определяет производительность, а это не про данное программное решение. Его задача определить скорость выполнения участка кода с ограниченной областью видимости. Бенчмарком здесь и не пахнет. Мало того, он и не претендует на него. Вы видите слово "benchmark" в описании? Вот именно.


          1. tzlom
            04.02.2023 15:10

            Бенчмаркинг это методика сравнения показателей между собой с целью установления отличия от эталонного. Нет никакой принципиальной разницы измеряете ли вы одну функцию, десяток вложенных или целую программу, важно только что вы даёте результаты которые можно сравнивать между собой. Выдавать усреднённые значения это разумный подход который позволяет сократить влияние внешних флуктуаций на измеряемое значение, но как я показал в другом комментарии этого не достаточно для заключения быстрее ли А чем Б или нет.


            1. Helldar Автор
              04.02.2023 15:27

              Как уже писал раннее, Runtime Comparison не претендует на точный инструмент.


              1. tzlom
                04.02.2023 15:32
                +3

                А потом в комментах вы сравниваете своим инструментом двойные кавычки с одинарными? Вы тролите что-ли?


      1. tzlom
        04.02.2023 14:59

        Наипростейший != правильный

        Начнём с microtime - какая у него точность? Идём в документацию и видим "For performance measurements, using hrtime() is recommended.", упс...

        Дальше лучше, смотрим на данные из параграфа "Округление значений"
        Казалось бы всё однозначно - минимум, максимум и среднее варианта А меньше варианта Б, однако проведём Т-тест

        > a = c(0.0112, 0.0147, 0.0153, .0157, .0154)
        > b = c(.015, .0155, .0153,.015, .0158)
        > summary(a)
           Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
        0.01120 0.01470 0.01530 0.01446 0.01540 0.01570 
        > summary(b)
           Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
        0.01500 0.01500 0.01530 0.01532 0.01550 0.01580 
        > sd(a)
        [1] 0.001858225
        > sd(b)
        [1] 0.0003420526
        > t.test(a, b)
        
                Welch Two Sample t-test
        
        data:  a and b
        t = -1.0178, df = 4.2708, p-value = 0.3629
        alternative hypothesis: true difference in means is not equal to 0
        95 percent confidence interval:
         -0.003148545  0.001428545
        sample estimates:
        mean of x mean of y 
          0.01446   0.01532 

        P-value говорит что основная гипотеза (распределения равны) верна, т.е. на самом деле там одинаковые распределения.

        google benchmark я привёл как инструмент который делает бенчмарки правильно - там много нюансов которые увеличивают точность измерений:

        • возможность задать код до и после измерений, чтобы подготовить данные и почистить за собой

        • автоматический подбор количества итераций — проще для пользователя и позволяет реагировать на случайные выбросы в статистике

        • перемешивание вариантов, т.к. последовательная долбёжка одного варианта показывает слишком оптимистичную картину при наличии кешей

        • авто подсчёт нужных статистик

        • исключение выбросов, к примеру если ОС тупанула во время измерений


        1. Helldar Автор
          04.02.2023 15:10
          -2

          Runtime Comparison не претендует на звание бенчмарка, а для проверки скорости участков кода microtime хватает за глаза.


          1. ysoft
            04.02.2023 16:32

            имеет смысл сделать, откидывание крайних значений, или брать например, 30%(задаваемо) лучших значений


            1. Helldar Автор
              04.02.2023 16:54

              Задаваемые значения точно не подойдут, т.к. пакет заранее не знает что именно передаст ему разработчик и в каком виде. Что касается крайних значений, именно поэтому и используется среднее арифметическое, что позволяет получать приблизительное время выполнения без плясок с бубном.


              1. ysoft
                04.02.2023 17:14
                +1

                имелось ввиду другое, у тебя например есть 100 прогонов, и ты можешь сказать - учесть только 30 из них лучших, т.к. у тебя также какие то процессы на компе могут испортить все показатели, а лучшие значение не изменяться


  1. derwin
    04.02.2023 20:15
    +1

    У Laravel есть из коробки что то похожее https://laravel.com/docs/9.x/helpers#benchmarking


    1. Helldar Автор
      04.02.2023 20:23

      Да, верно. Этот же пакет фреймворко-независимый.

      Кстати, бенчмарк в Laravel выводит лишь время.

      Например:

      $range = [0, 1000000];
      
      Benchmark::dd([
          'rand' => fn () => rand(...$range),
          'mt_rand' => fn () => mt_rand(...$range),
          'random_int' => fn () => random_int(...$range),
      ], 100);
      array:3 [
        "rand" => "0.001ms"
        "mt_rand" => "0.002ms"
        "random_int" => "0.001ms"
      ]

      Если этого достаточно, то вполне годный инструмент.

      Под капотом та же самая функция hrtime с последующим расчётом среднего значения из списка результатов.