├── LICENSE ├── README.md ├── composer.json └── src ├── Benchmark.php ├── Contracts └── Transformer.php ├── Exceptions └── ValueIsNotCallableException.php ├── Services ├── Arr.php ├── MeasurementError.php ├── Memory.php ├── Runner.php └── View.php ├── Transformers ├── Base.php ├── Separator.php ├── Stats.php ├── Times.php ├── Transformer.php └── Winner.php └── View ├── ProgressBar.php └── Table.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Andrey Helldar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | ![the dragon code benchmark](https://preview.dragon-code.pro/the-dragon-code/benchmark.svg?brand=php) 4 | 5 | [![Stable Version][badge_stable]][link_packagist] 6 | [![Total Downloads][badge_downloads]][link_packagist] 7 | [![Github Workflow Status][badge_build]][link_build] 8 | [![License][badge_license]][link_license] 9 | 10 | ## Installation 11 | 12 | To get the latest version of `The Dragon Code: Benchmark`, simply require the project 13 | using [Composer](https://getcomposer.org): 14 | 15 | ```bash 16 | composer require dragon-code/benchmark --dev 17 | ``` 18 | 19 | Or manually update `require-dev` block of `composer.json` and run `composer update` console command: 20 | 21 | ```json 22 | { 23 | "require-dev": { 24 | "dragon-code/benchmark": "^3.0" 25 | } 26 | } 27 | ``` 28 | 29 | ## Using 30 | 31 | > Note 32 | > 33 | > The result of the execution is printed to the console, so make sure you call the code from the console. 34 | 35 | ```php 36 | use DragonCode\Benchmark\Benchmark; 37 | 38 | Benchmark::start()->compare( 39 | fn () => /* some code */, 40 | fn () => /* some code */, 41 | ); 42 | 43 | Benchmark::start()->compare([ 44 | fn () => /* some code */, 45 | fn () => /* some code */, 46 | ]); 47 | 48 | Benchmark::start()->compare([ 49 | 'foo' => fn () => /* some code */, 50 | 'bar' => fn () => /* some code */, 51 | ]); 52 | ``` 53 | 54 | Result example: 55 | 56 | ``` 57 | ------- --------------------- -------------------- 58 | # 0 1 59 | ------- --------------------- -------------------- 60 | 1 0.0101 ms - 64.8Kb 0.0014 ms - 13.7Kb 61 | 2 0.0099 ms - 59.5Kb 0.0011 ms - 12.9Kb 62 | 3 0.0014 ms - 59.1Kb 0.001 ms - 12.5Kb 63 | 4 0.0012 ms - 58.8Kb 0.0011 ms - 12.2Kb 64 | 5 0.0012 ms - 58.4Kb 0.0011 ms - 11.8Kb 65 | 6 0.0011 ms - 58Kb 0.001 ms - 11.4Kb 66 | 7 0.0011 ms - 57.6Kb 0.001 ms - 11Kb 67 | 8 0.0011 ms - 57.3Kb 0.001 ms - 10.7Kb 68 | 9 0.0012 ms - 56.6Kb 0.001 ms - 10Kb 69 | ... 70 | 100 0.0011 ms - 14.8Kb 0.0011 ms - 4.1Kb 71 | ------- --------------------- -------------------- 72 | min 0.001 ms - 14.8Kb 0.001 ms - 4.1Kb 73 | max 0.0101 ms - 64.8Kb 0.0026 ms - 13.7Kb 74 | avg 0.00122 ms - 47.5Kb 0.0016 ms - 4.1Kb 75 | total 0.7998 ms 0.6156 ms 76 | ------- --------------------- -------------------- 77 | Order - 1 - - 2 - 78 | ------- --------------------- -------------------- 79 | ``` 80 | 81 | When measuring the average value among the results, when more than 10 iterations are used, the final data is filtered by 82 | peak values. The calculation of the 10% of the lowest and 83 | 10% of the highest values is excluded from the total result, thus the final data becomes cleaner and less dependent on 84 | any external factors. 85 | 86 | ### Iterations Count 87 | 88 | By default, the benchmark performs 100 iterations per callback, but you can change this number by calling 89 | the `iterations` method: 90 | 91 | ```php 92 | use DragonCode\Benchmark\Benchmark; 93 | 94 | Benchmark::start() 95 | ->iterations(5) 96 | ->compare( 97 | fn () => /* some code */, 98 | fn () => /* some code */, 99 | ); 100 | ``` 101 | 102 | If the passed value is less than 1, then one iteration will be performed for each callback. 103 | 104 | ``` 105 | ------- --------------------- --------------------- 106 | # 0 1 107 | ------- --------------------- --------------------- 108 | 1 0.0077 ms - 64.8Kb 0.0015 ms - 57.3Kb 109 | 2 0.0023 ms - 59.5Kb 0.0013 ms - 56.5Kb 110 | 3 0.0013 ms - 59.1Kb 0.0012 ms - 56.1Kb 111 | 4 0.0012 ms - 58.8Kb 0.0011 ms - 55.8Kb 112 | 5 0.0011 ms - 58.4Kb 0.0011 ms - 55.4Kb 113 | ------- --------------------- --------------------- 114 | min 0.0011 ms - 58.4Kb 0.0011 ms - 55.4Kb 115 | max 0.0077 ms - 64.8Kb 0.0015 ms - 57.3Kb 116 | avg 0.00272 ms - 60.1Kb 0.00124 ms - 56.2Kb 117 | total 0.2453 ms 0.1105 ms 118 | ------- --------------------- --------------------- 119 | Order - 2 - - 1 - 120 | ------- --------------------- --------------------- 121 | ``` 122 | 123 | You can also get the number of the current execution iteration from the input parameter: 124 | 125 | ```php 126 | use DragonCode\Benchmark\Benchmark; 127 | 128 | Benchmark::start() 129 | ->iterations(5) 130 | ->compare( 131 | fn (int $iteration) => /* some code */, 132 | fn (int $iteration) => /* some code */, 133 | ); 134 | ``` 135 | 136 | ### Without Data 137 | 138 | If you want to see only the summary result of the run time without detailed information for each iteration, then you can 139 | call the `withoutData` method, which will display only the 140 | summary information: 141 | 142 | ```php 143 | use DragonCode\Benchmark\Benchmark; 144 | 145 | Benchmark::start() 146 | ->withoutData() 147 | ->compare([ 148 | 'foo' => fn () => /* some code */, 149 | 'bar' => fn () => /* some code */, 150 | ]); 151 | ``` 152 | 153 | Result example: 154 | 155 | ``` 156 | ------- ------------------- ------------------- 157 | # foo bar 158 | ------- ------------------- ------------------- 159 | min 12.02 ms - 58.4Kb 14.71 ms - 55.4Kb 160 | max 15.66 ms - 64.8Kb 15.67 ms - 57.3Kb 161 | avg 14.65 ms - 60.1Kb 15.17 ms - 56.2Kb 162 | total 73.93 ms 76.31 ms 163 | ------- ------------------- ------------------- 164 | Order - 1 - - 2 - 165 | ------- ------------------- ------------------- 166 | ``` 167 | 168 | > Note 169 | > 170 | > If the option to display detailed information is enabled (without using the `withoutData` method) and more than 1000 171 | > iterations are requested, then the output of detailed 172 | > information will be forcibly disabled, since there will be absolutely no point in it with a significantly increasing 173 | > load on the computer. 174 | 175 | ### Round Precision 176 | 177 | By default, the script does not round measurement results, but you can specify the number of decimal places to which 178 | rounding can be performed. 179 | 180 | For example: 181 | 182 | ```php 183 | use DragonCode\Benchmark\Benchmark; 184 | 185 | Benchmark::start() 186 | ->iterations(5) 187 | ->round(2) 188 | ->compare( 189 | fn () => /* some code */, 190 | fn () => /* some code */, 191 | ); 192 | ``` 193 | 194 | Result example: 195 | 196 | ``` 197 | ------- ------------------ ------------------ 198 | # 0 1 199 | ------- ------------------ ------------------ 200 | 1 11.85 ms - 0b 15.22 ms - 0b 201 | 2 14.56 ms - 0b 14.94 ms - 0b 202 | 3 15.4 ms - 0b 14.99 ms - 0b 203 | 4 15.3 ms - 0b 15.24 ms - 0b 204 | 5 14.76 ms - 0b 15.14 ms - 0b 205 | ------- ------------------ ------------------ 206 | min 11.85 ms - 0b 14.94 ms - 0b 207 | max 15.4 ms - 0b 15.24 ms - 0b 208 | avg 14.37 ms - 0b 15.11 ms - 0b 209 | total 73.47 ms 76.03 ms 210 | ------- ------------------ ------------------ 211 | Order - 1 - - 2 - 212 | ------- ------------------ ------------------ 213 | ``` 214 | 215 | ### Prepare Data 216 | 217 | In some cases, it becomes necessary to call some action before starting each check cycle so that its time does not fall 218 | into the result of the runtime check. 219 | There is a `prepare` method for this: 220 | 221 | ```php 222 | use DragonCode\Benchmark\Benchmark; 223 | 224 | Benchmark::start() 225 | ->prepare(fn () => /* some code */) 226 | ->compare( 227 | fn () => /* some code */, 228 | fn () => /* some code */, 229 | ); 230 | ``` 231 | 232 | When calling a callback, the name and iteration parameters are passed to it. If necessary, you can use this information 233 | inside the callback function. 234 | 235 | ```php 236 | use DragonCode\Benchmark\Benchmark; 237 | 238 | Benchmark::start() 239 | ->prepare(fn (mixed $name, int $iteration) => /* some code */) 240 | ->compare( 241 | fn () => /* some code */, 242 | fn () => /* some code */, 243 | ); 244 | ``` 245 | 246 | You can also get the number of the current iteration and the result of the execution of the preliminary function from 247 | the input parameter: 248 | 249 | ```php 250 | use DragonCode\Benchmark\Benchmark; 251 | 252 | Benchmark::start() 253 | ->prepare(fn (mixed $name, int $iteration) => /* some code */) 254 | ->compare( 255 | fn (int $iteration, mixed $prepareResult) => /* some code */, 256 | fn (int $iteration, mixed $prepareResult) => /* some code */, 257 | ); 258 | ``` 259 | 260 | ## Information 261 | 262 | ``` 263 | ------- ------------------ ------------------ 264 | # foo bar 265 | ------- ------------------ ------------------ 266 | 1 11.33 ms - 0b 14.46 ms - 0b 267 | 2 14.63 ms - 0b 14.8 ms - 0b 268 | 3 14.72 ms - 0b 15.02 ms - 0b 269 | 4 15.28 ms - 0b 15.04 ms - 0b 270 | N+1 15.03 ms - 0b 15.09 ms - 0b 271 | ------- ------------------ ------------------ 272 | min 11.33 ms - 0b 14.46 ms - 0b 273 | max 15.28 ms - 0b 15.09 ms - 0b 274 | avg 14.2 ms - 0b 14.88 ms - 0b 275 | total 71.62 ms 75.12 ms 276 | ------- ------------------ ------------------ 277 | Order - 1 - - 2 - 278 | ------- ------------------ ------------------ 279 | ``` 280 | 281 | * `foo`, `bar` - The names of the columns in the passed array. Needed for identification. By default, the array index is 282 | used, starting from zero. For example, `1, 2, 3,.. N+1`. 283 | * `1`, `2`, `3`, ..., `N+1` - Verification iteration sequence number. 284 | * `11.33 ms` - Execution time of the checked code in one iteration. 285 | * `0b`, `6.8Kb`, etc. - The amount of RAM used by the checked code. 286 | * `min` - Minimum code processing time. 287 | * `max` - Maximum code processing time. 288 | * `avg` - The arithmetic mean value among all iterations, taking into account the elimination of 10% of the smallest and 289 | 10% of the largest values to obtain a more accurate value 290 | through the possible intervention of external factors. 291 | * `total` - The total time and RAM spent on checking all iterations of the code. 292 | 293 | ## License 294 | 295 | This package is licensed under the [MIT License](LICENSE). 296 | 297 | 298 | [badge_build]: https://img.shields.io/github/actions/workflow/status/TheDragonCode/benchmark/phpunit.yml?style=flat-square 299 | 300 | [badge_downloads]: https://img.shields.io/packagist/dt/dragon-code/benchmark.svg?style=flat-square 301 | 302 | [badge_license]: https://img.shields.io/packagist/l/dragon-code/benchmark.svg?style=flat-square 303 | 304 | [badge_stable]: https://img.shields.io/github/v/release/TheDragonCode/benchmark?label=stable&style=flat-square 305 | 306 | [link_build]: https://github.com/TheDragonCode/benchmark/actions 307 | 308 | [link_license]: LICENSE 309 | 310 | [link_packagist]: https://packagist.org/packages/dragon-code/benchmark 311 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragon-code/benchmark", 3 | "description": "Simple comparison of code execution speed between different options", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "benchmark", 8 | "runtime", 9 | "comparison", 10 | "comparison-tool", 11 | "time", 12 | "timer", 13 | "comparator", 14 | "comparators", 15 | "speed", 16 | "speedtest" 17 | ], 18 | "authors": [ 19 | { 20 | "name": "Andrey Helldar", 21 | "email": "helldar@dragon-code.pro", 22 | "homepage": "https://dragon-code.pro" 23 | } 24 | ], 25 | "support": { 26 | "issues": "https://github.com/TheDragonCode/benchmark/issues", 27 | "source": "https://github.com/TheDragonCode/benchmark" 28 | }, 29 | "funding": [ 30 | { 31 | "type": "boosty", 32 | "url": "https://boosty.to/dragon-code" 33 | }, 34 | { 35 | "type": "yoomoney", 36 | "url": "https://yoomoney.ru/to/410012608840929" 37 | } 38 | ], 39 | "require": { 40 | "php": "^8.2", 41 | "dragon-code/support": "^6.10", 42 | "symfony/console": "^6.0 || ^7.0" 43 | }, 44 | "require-dev": { 45 | "phpunit/phpunit": "^11.0 || ^12.0", 46 | "symfony/var-dumper": "^6.0 || ^7.0" 47 | }, 48 | "minimum-stability": "stable", 49 | "prefer-stable": true, 50 | "autoload": { 51 | "psr-4": { 52 | "DragonCode\\Benchmark\\": "src/" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "Tests\\": "tests/" 58 | } 59 | }, 60 | "config": { 61 | "allow-plugins": { 62 | "dragon-code/codestyler": true, 63 | "ergebnis/composer-normalize": true, 64 | "friendsofphp/php-cs-fixer": true, 65 | "symfony/thanks": true 66 | }, 67 | "preferred-install": "dist", 68 | "sort-packages": true 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Benchmark.php: -------------------------------------------------------------------------------- 1 | [], 36 | 'total' => [], 37 | ]; 38 | 39 | public function __construct( 40 | protected Runner $runner = new Runner(), 41 | protected Transformer $transformer = new Transformer() 42 | ) { 43 | $this->view = new View( 44 | new SymfonyStyle( 45 | new ArgvInput(), 46 | new ConsoleOutput() 47 | ) 48 | ); 49 | } 50 | 51 | public static function start(): static 52 | { 53 | return new static(); 54 | } 55 | 56 | public function prepare(callable $callback): self 57 | { 58 | $this->prepare = $callback; 59 | 60 | return $this; 61 | } 62 | 63 | public function iterations(int $count): self 64 | { 65 | $this->iterations = max(1, $count); 66 | 67 | return $this; 68 | } 69 | 70 | public function round(?int $precision): self 71 | { 72 | $this->view->setRound($precision); 73 | 74 | return $this; 75 | } 76 | 77 | public function withoutData(): self 78 | { 79 | $this->withData = false; 80 | 81 | return $this; 82 | } 83 | 84 | public function compare(array|callable ...$callbacks): void 85 | { 86 | $values = is_array($callbacks[0]) ? $callbacks[0] : func_get_args(); 87 | 88 | $this->withProgress($values, $this->stepsCount($values)); 89 | $this->show(); 90 | } 91 | 92 | protected function withProgress(array $callbacks, int $count): void 93 | { 94 | $bar = $this->view->progressBar()->create($count); 95 | 96 | $this->chunks($callbacks, $bar); 97 | 98 | $bar->finish(); 99 | $this->view->emptyLine(2); 100 | } 101 | 102 | protected function stepsCount(array $callbacks): int 103 | { 104 | return count($callbacks) * $this->iterations; 105 | } 106 | 107 | protected function chunks(array $callbacks, ProgressBarService $progressBar): void 108 | { 109 | foreach ($callbacks as $name => $callback) { 110 | $this->validate($callback); 111 | 112 | $this->each($name, $callback, $progressBar); 113 | } 114 | } 115 | 116 | protected function each(mixed $name, callable $callback, ProgressBarService $progressBar): void 117 | { 118 | $this->result['total'][$name] = $this->call( 119 | fn () => $this->run($name, $callback, $progressBar) 120 | ); 121 | } 122 | 123 | protected function run(mixed $name, callable $callback, ProgressBarService $progressBar): void 124 | { 125 | for ($i = 1; $i <= $this->iterations; ++$i) { 126 | $result = $this->runPrepare($name, $i); 127 | 128 | [$time, $ram] = $this->call($callback, [$i, $result]); 129 | 130 | $this->push($name, $i, $time, $ram); 131 | 132 | $progressBar->advance(); 133 | } 134 | } 135 | 136 | protected function runPrepare(mixed $name, int $iteration): mixed 137 | { 138 | if ($callback = $this->prepare) { 139 | return $callback($name, $iteration); 140 | } 141 | 142 | return null; 143 | } 144 | 145 | protected function call(callable $callback, array $parameters = []): array 146 | { 147 | return $this->runner->call($callback, $parameters); 148 | } 149 | 150 | protected function push(mixed $name, int $iteration, float $time, float $ram): void 151 | { 152 | $this->result['each'][$name][$iteration]['time'] = $time; 153 | $this->result['each'][$name][$iteration]['ram'] = $ram; 154 | } 155 | 156 | protected function show(): void 157 | { 158 | $table = $this->withData() ? $this->transformer->forTime($this->result['each']) : []; 159 | 160 | $stats = $this->transformer->forStats($this->result); 161 | $winner = $this->transformer->forWinners($stats); 162 | 163 | $this->view->table($this->transformer->merge($table, $stats, $winner)); 164 | } 165 | 166 | protected function withData(): bool 167 | { 168 | return $this->withData && $this->iterations <= 1000; 169 | } 170 | 171 | protected function validate(mixed $callback): void 172 | { 173 | if (! is_callable($callback)) { 174 | throw new ValueIsNotCallableException(gettype($callback)); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Contracts/Transformer.php: -------------------------------------------------------------------------------- 1 | get($a, $key) : $a; 39 | $b = is_array($b) ? $this->get($b, $key) : $b; 40 | 41 | if ($a === $b) { 42 | return 0; 43 | } 44 | 45 | return $a < $b ? -1 : 1; 46 | }); 47 | 48 | return $data; 49 | } 50 | 51 | public function pluck(array $data, string $value, ?string $key = null): array 52 | { 53 | $result = []; 54 | 55 | $index = 0; 56 | 57 | foreach ($data as $item) { 58 | $position = $key ? $item[$key] : $index; 59 | 60 | $result[$position] = $item[$value]; 61 | 62 | ++$index; 63 | } 64 | 65 | return $result; 66 | } 67 | 68 | public function flip(array $data): array 69 | { 70 | return array_flip($data); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Services/MeasurementError.php: -------------------------------------------------------------------------------- 1 | count($data); 23 | 24 | return $this->disabled($count) ? $data : $this->partial($data, $count); 25 | } 26 | 27 | protected function partial(array $data, int $count): array 28 | { 29 | return array_slice($this->sort($data), $this->take($count), $this->offset($count)); 30 | } 31 | 32 | protected function offset(int $count): int 33 | { 34 | return (int) ($count * $this->percent); 35 | } 36 | 37 | protected function take(int $count): int 38 | { 39 | return $count - (2 * $this->offset($count)); 40 | } 41 | 42 | protected function count(array $values): int 43 | { 44 | return count($values); 45 | } 46 | 47 | protected function disabled(int $count): bool 48 | { 49 | return $count <= $this->minCount; 50 | } 51 | 52 | protected function sort(array $values): array 53 | { 54 | return $this->arr->sort($values); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Services/Memory.php: -------------------------------------------------------------------------------- 1 | 1024 * 1024 * 1024, 16 | 'MB' => 1024 * 1024, 17 | 'KB' => 1024, 18 | ]; 19 | 20 | public function now(): int 21 | { 22 | return memory_get_peak_usage(true); 23 | } 24 | 25 | public function diff(int $memory): int 26 | { 27 | return memory_get_peak_usage(true) - $memory; 28 | } 29 | 30 | public function reset(): void 31 | { 32 | gc_collect_cycles(); 33 | memory_reset_peak_usage(); 34 | } 35 | 36 | public function format(int $bytes): string 37 | { 38 | foreach ($this->sizes as $unit => $value) { 39 | if ($bytes >= $value) { 40 | return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); 41 | } 42 | } 43 | 44 | return $bytes . ' byte' . ($bytes !== 1 ? 's' : ''); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Services/Runner.php: -------------------------------------------------------------------------------- 1 | clean(); 18 | 19 | return $this->run($callback, $parameters); 20 | } 21 | 22 | protected function clean(): void 23 | { 24 | $this->memory->reset(); 25 | } 26 | 27 | protected function run(callable $callback, array $parameters = []): array 28 | { 29 | $ramFrom = $this->memory->now(); 30 | $startAt = hrtime(true); 31 | 32 | $callback(...$parameters); 33 | 34 | $time = $this->diff(hrtime(true), $startAt); 35 | $ram = $this->memory->diff($ramFrom); 36 | 37 | return [$time, $ram]; 38 | } 39 | 40 | protected function diff(float $now, float $startAt): float 41 | { 42 | return ($now - $startAt) / 1e+6; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Services/View.php: -------------------------------------------------------------------------------- 1 | table = new Table($this->io); 29 | $this->progressBar = new ProgressBar($this->io); 30 | } 31 | 32 | public function setRound(?int $precision): void 33 | { 34 | $this->roundPrecision = $precision; 35 | } 36 | 37 | public function table(array $data): void 38 | { 39 | $this->table->show( 40 | $this->appendMs($data) 41 | ); 42 | } 43 | 44 | public function progressBar(): ProgressBar 45 | { 46 | return $this->progressBar; 47 | } 48 | 49 | public function emptyLine(int $count = 1): void 50 | { 51 | $this->io->newLine($count); 52 | } 53 | 54 | protected function appendMs(array $data): array 55 | { 56 | foreach ($data as &$values) { 57 | foreach ($values as $key => &$value) { 58 | if ($key === '#' || ! is_array($value)) { 59 | continue; 60 | } 61 | 62 | $time = $this->roundTime($value['time']); 63 | $ram = $this->roundRam($value['ram'] ?? null); 64 | 65 | $value = ! empty($ram) 66 | ? sprintf('%s ms - %s', $time, $ram) 67 | : sprintf('%s ms', $time); 68 | } 69 | } 70 | 71 | return $data; 72 | } 73 | 74 | protected function roundTime(float $value): float 75 | { 76 | if (is_numeric($this->roundPrecision)) { 77 | return round($value, $this->roundPrecision); 78 | } 79 | 80 | return $value; 81 | } 82 | 83 | protected function roundRam(float|int|null $bytes): ?string 84 | { 85 | if ($bytes !== null) { 86 | return $this->memory->format((int) $bytes); 87 | } 88 | 89 | return null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Transformers/Base.php: -------------------------------------------------------------------------------- 1 | calculate($data); 32 | } 33 | 34 | protected function calculate(array $data): array 35 | { 36 | $items = []; 37 | 38 | foreach ($data['each'] as $name => $iterations) { 39 | foreach ($this->methods as $method) { 40 | $this->put($items, $method, $name, fn () => call_user_func([$this, $method], $iterations)); 41 | } 42 | } 43 | 44 | foreach ($data['total'] as $name => $value) { 45 | $this->put($items, 'total', $name, fn () => ['time' => $value[0]]); 46 | } 47 | 48 | return $items; 49 | } 50 | 51 | protected function min(array $values): array 52 | { 53 | return [ 54 | 'time' => min($this->arr->pluck($values, 'time')), 55 | 'ram' => min($this->arr->pluck($values, 'ram')), 56 | ]; 57 | } 58 | 59 | protected function max(array $values): array 60 | { 61 | return [ 62 | 'time' => max($this->arr->pluck($values, 'time')), 63 | 'ram' => max($this->arr->pluck($values, 'ram')), 64 | ]; 65 | } 66 | 67 | protected function avg(array $values): array 68 | { 69 | $values = $this->filter($values); 70 | 71 | return [ 72 | 'time' => array_sum($this->arr->pluck($values, 'time')) / count($values), 73 | 'ram' => array_sum($this->arr->pluck($values, 'ram')) / count($values), 74 | ]; 75 | } 76 | 77 | protected function filter(array $values): array 78 | { 79 | return $this->measurementError->filter($values); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Transformers/Times.php: -------------------------------------------------------------------------------- 1 | $values) { 16 | foreach ($values as $iteration => $value) { 17 | $items[$iteration]['#'] = $iteration; 18 | 19 | $items[$iteration][$name]['time'] = $value['time']; 20 | $items[$iteration][$name]['ram'] = $value['ram']; 21 | } 22 | } 23 | 24 | return array_values($items); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Transformers/Transformer.php: -------------------------------------------------------------------------------- 1 | resolve(Times::class, $data); 17 | } 18 | 19 | public function forStats(array $data): array 20 | { 21 | return $this->resolve(Stats::class, $data); 22 | } 23 | 24 | public function forWinners(array $data): array 25 | { 26 | return $this->resolve(Winner::class, $data); 27 | } 28 | 29 | public function separator(array $data): array 30 | { 31 | return $this->resolve(Separator::class, $data); 32 | } 33 | 34 | public function merge(array ...$arrays): array 35 | { 36 | $result = []; 37 | 38 | $count = count($arrays); 39 | $index = 1; 40 | 41 | foreach ($arrays as $array) { 42 | if (! empty($array)) { 43 | $result = $index < $count 44 | ? array_merge($result, $array, $this->separator($arrays[0])) 45 | : array_merge($result, $array); 46 | } 47 | 48 | ++$index; 49 | } 50 | 51 | return $result; 52 | } 53 | 54 | protected function resolve(string|TransformerContract $transformer, array $data): array 55 | { 56 | return (new $transformer())->transform($data); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Transformers/Winner.php: -------------------------------------------------------------------------------- 1 | '- %d -', 19 | 2 => '- %d -', 20 | 3 => '- %d -', 21 | ]; 22 | 23 | public function __construct( 24 | protected Arr $arr = new Arr() 25 | ) {} 26 | 27 | public function transform(array $data): array 28 | { 29 | $values = $data[$this->key]; 30 | 31 | $names = $this->prepare($values); 32 | 33 | return $this->order($values, $names); 34 | } 35 | 36 | protected function order(array $data, array $names): array 37 | { 38 | $items = []; 39 | 40 | foreach (array_keys($data) as $key) { 41 | if ($key === '#') { 42 | continue; 43 | } 44 | 45 | $position = $names[$key] + 1; 46 | 47 | $this->put($items, 'Order', $key, fn () => $this->color($position)); 48 | } 49 | 50 | return $items; 51 | } 52 | 53 | protected function prepare(array $data): array 54 | { 55 | $data = $this->arr->forget($data, '#'); 56 | $data = $this->arr->map($data, fn (array $values, mixed $name) => compact('name', 'values')); 57 | $data = $this->arr->sort($data, 'values.time'); 58 | $data = $this->arr->pluck($data, 'name'); 59 | 60 | return $this->arr->flip($data); 61 | } 62 | 63 | protected function color(int $order): string 64 | { 65 | if ($template = $this->orders[$order] ?? null) { 66 | return sprintf($template, $order); 67 | } 68 | 69 | return sprintf('- %d -', $order); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/View/ProgressBar.php: -------------------------------------------------------------------------------- 1 | io->createProgressBar($count); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/View/Table.php: -------------------------------------------------------------------------------- 1 | io->table($this->headers($data), $data); 21 | } 22 | 23 | protected function headers(array $data): array 24 | { 25 | return array_keys(array_values($data)[0]); 26 | } 27 | } 28 | --------------------------------------------------------------------------------