From 3329b8576c07546d21f6f33b931d7254a8606628 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 1 May 2026 21:54:32 +0300 Subject: [PATCH 1/5] Add warmup support to benchmark iterations and tests --- src/Benchmark.php | 23 ++++++++++++--- .../warmup_with_data_set___10__.snap | 16 +++++++++++ .../warmup_with_data_set___1__.snap | 16 +++++++++++ .../warmup_with_data_set___5__.snap | 16 +++++++++++ tests/Unit/WarmUp/WarmUpTest.php | 28 +++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___10__.snap create mode 100644 tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___1__.snap create mode 100644 tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___5__.snap create mode 100644 tests/Unit/WarmUp/WarmUpTest.php diff --git a/src/Benchmark.php b/src/Benchmark.php index ec2345c..3b283b4 100644 --- a/src/Benchmark.php +++ b/src/Benchmark.php @@ -27,6 +27,8 @@ class Benchmark protected int $deviations = 1; + protected int $warmup = 0; + public function __construct( protected RunnerService $runner = new RunnerService, protected ViewService $view = new ViewService, @@ -47,7 +49,6 @@ public static function make(): static /** * Sets the directory for storing benchmark snapshots. * - * @param string $directory * * @return $this */ @@ -58,6 +59,13 @@ public function snapshots(string $directory): static return $this; } + public function warmup(int $count = 1): static + { + $this->warmup = max(1, abs($count)); + + return $this; + } + /** * Sets a callback to be executed before all iterations for each comparison. * @@ -293,7 +301,10 @@ protected function withProgress(Closure $callback, int $total): void */ protected function steps(array $callbacks, int $multiplier = 1): int { - return count($callbacks) * $this->iterations * $multiplier; + $count = count($callbacks); + $warmup = $count * $this->warmup; + + return $count * $this->iterations * $multiplier + $warmup; } /** @@ -322,14 +333,18 @@ protected function chunks(array $callbacks, ProgressBar $progressBar): void */ protected function run(mixed $name, Closure $callback, ProgressBar $progressBar): void { - for ($i = 1; $i <= $this->iterations; $i++) { + $warmedUp = $this->warmup === 0; + + for ($i = 1; $i <= $this->iterations + $this->warmup; $i++) { $result = $this->callbacks->performBeforeEach($name, $i); [$time, $memory] = $this->call($callback, [$i, $result]); $this->callbacks->performAfterEach($name, $i, $time, $memory); - $this->push($name, $time, $memory); + if ($warmedUp) { + $this->push($name, $time, $memory); + } $progressBar->advance(); } diff --git a/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___10__.snap b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___10__.snap new file mode 100644 index 0000000..34b8915 --- /dev/null +++ b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___10__.snap @@ -0,0 +1,16 @@ + + + 0/26 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% 1/26 [▓░░░░░░░░░░░░░░░░░░░░░░░░░░░] 3% 2/26 [▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░] 7% 3/26 [▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░] 11% 4/26 [▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░] 15% 5/26 [▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░] 19% 6/26 [▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░] 23% 7/26 [▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░] 26% 8/26 [▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░] 30% 9/26 [▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░] 34% 10/26 [▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░] 38% 11/26 [▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░] 42% 12/26 [▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░] 46% 13/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░] 50% 14/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░] 53% 15/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░] 57% 16/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░] 61% 17/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░] 65% 18/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░] 69% 19/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░] 73% 20/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░] 76% 21/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░] 80% 22/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░] 84% 23/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░] 88% 24/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░] 92% 25/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░] 96% 26/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% 26/26 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + ++-------+------------------------+------------------------+ +| # | 0 | 1 | ++-------+------------------------+------------------------+ +| min | 15.6789 ms - 202 bytes | 2.3457 ms - 102 bytes | +| max | 112.789 ms - 209 bytes | 9.7568 ms - 109 bytes | +| avg | 53.0252 ms - 205 bytes | 5.9429 ms - 105 bytes | +| total | 424.202 ms - 1.61 KB | 47.5432 ms - 844 bytes | ++-------+------------------------+------------------------+ +| order | 2 | 1 | ++-------+------------------------+------------------------+ diff --git a/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___1__.snap b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___1__.snap new file mode 100644 index 0000000..bd2ccfb --- /dev/null +++ b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___1__.snap @@ -0,0 +1,16 @@ + + + 0/8 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% 1/8 [▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░] 12% 2/8 [▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░] 25% 3/8 [▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░] 37% 4/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░] 50% 5/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░] 62% 6/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░] 75% 7/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░] 87% 8/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% 8/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + ++-------+------------------------+------------------------+ +| # | 0 | 1 | ++-------+------------------------+------------------------+ +| min | 15.6789 ms - 202 bytes | 2.3457 ms - 102 bytes | +| max | 112.789 ms - 209 bytes | 9.7568 ms - 109 bytes | +| avg | 53.0252 ms - 205 bytes | 5.9429 ms - 105 bytes | +| total | 424.202 ms - 1.61 KB | 47.5432 ms - 844 bytes | ++-------+------------------------+------------------------+ +| order | 2 | 1 | ++-------+------------------------+------------------------+ diff --git a/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___5__.snap b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___5__.snap new file mode 100644 index 0000000..f070740 --- /dev/null +++ b/tests/.pest/snapshots/Unit/WarmUp/WarmUpTest/warmup_with_data_set___5__.snap @@ -0,0 +1,16 @@ + + + 0/16 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% 1/16 [▓░░░░░░░░░░░░░░░░░░░░░░░░░░░] 6% 2/16 [▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░] 12% 3/16 [▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░] 18% 4/16 [▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░] 25% 5/16 [▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░] 31% 6/16 [▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░] 37% 7/16 [▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░] 43% 8/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░] 50% 9/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░] 56% 10/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░] 62% 11/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░] 68% 12/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░] 75% 13/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░] 81% 14/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░] 87% 15/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░] 93% 16/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% 16/16 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% + + + ++-------+------------------------+------------------------+ +| # | 0 | 1 | ++-------+------------------------+------------------------+ +| min | 15.6789 ms - 202 bytes | 2.3457 ms - 102 bytes | +| max | 112.789 ms - 209 bytes | 9.7568 ms - 109 bytes | +| avg | 53.0252 ms - 205 bytes | 5.9429 ms - 105 bytes | +| total | 424.202 ms - 1.61 KB | 47.5432 ms - 844 bytes | ++-------+------------------------+------------------------+ +| order | 2 | 1 | ++-------+------------------------+------------------------+ diff --git a/tests/Unit/WarmUp/WarmUpTest.php b/tests/Unit/WarmUp/WarmUpTest.php new file mode 100644 index 0000000..11ea0ce --- /dev/null +++ b/tests/Unit/WarmUp/WarmUpTest.php @@ -0,0 +1,28 @@ +iterations(3) + ->round(4) + ->warmup($warmup) + ->compare( + foo: function () use (&$count) { + $count++; + }, + bar: function () use (&$count) { + $count++; + }, + ) + ->toConsole(); + + expect($count)->toBe(6 + $warmup * 2); + + expectOutputToMatchSnapshot(); +})->with([1, 5, 10]); From f173437027d86b9716465786e59e415858c2bbc9 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 1 May 2026 21:57:05 +0300 Subject: [PATCH 2/5] Add `warmup` method to configure warm-up iterations for benchmarks --- src/Benchmark.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Benchmark.php b/src/Benchmark.php index 3b283b4..e74b4db 100644 --- a/src/Benchmark.php +++ b/src/Benchmark.php @@ -49,7 +49,6 @@ public static function make(): static /** * Sets the directory for storing benchmark snapshots. * - * * @return $this */ public function snapshots(string $directory): static @@ -59,6 +58,12 @@ public function snapshots(string $directory): static return $this; } + /** + * Specifies the number of warm-up iterations for each callback. + * + * @param int<1, max> $count + * @return $this + */ public function warmup(int $count = 1): static { $this->warmup = max(1, abs($count)); From e5e7c31a93b6451640343f25f9fc4fe128630fd0 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 1 May 2026 21:59:36 +0300 Subject: [PATCH 3/5] Document `warmup` method for benchmark iterations --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 12d5074..aff4a93 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,28 @@ new Benchmark() ->toConsole(); ``` +### Warm-up + +Use the `warmup` method to perform a number of "warm-up" iterations for each callback before the measured run. +Warm-up iterations are executed exactly like regular ones (including `beforeEach` / `afterEach` callbacks and progress bar advancement), +but their time and memory results are excluded from the final statistics. + +This is useful for stabilizing measurements by mitigating the effects of cold caches, JIT/opcache priming, lazy autoloading +and other one-time initialization costs. + +By default, no warm-up is performed. Passing a value less than `1` is normalized to `1`. + +```php +use DragonCode\Benchmark\Benchmark; + +new Benchmark() + ->warmup(3) // run 3 warm-up iterations per callback before measuring + ->compare( + foo: fn () => /* some code */, + bar: fn () => /* some code */, + ); +``` + ### Round Precision Use the `round` method to set the number of decimal places in console output: From 7a8555d0f1b7c4bc8c15cfad9d9324752f1b6ece Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 1 May 2026 22:01:29 +0300 Subject: [PATCH 4/5] Refine `warmup` method documentation in README --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aff4a93..f413f5b 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,13 @@ new Benchmark() ### Warm-up -Use the `warmup` method to perform a number of "warm-up" iterations for each callback before the measured run. -Warm-up iterations are executed exactly like regular ones (including `beforeEach` / `afterEach` callbacks and progress bar advancement), -but their time and memory results are excluded from the final statistics. +Use the `warmup` method to run each callback a few times before the measured iterations. Warm-up runs behave +identically to regular ones (`beforeEach` / `afterEach` are invoked, the progress bar advances), but their +time and memory are not included in the final statistics. -This is useful for stabilizing measurements by mitigating the effects of cold caches, JIT/opcache priming, lazy autoloading -and other one-time initialization costs. +This stabilizes results by absorbing one-time costs such as cold caches, JIT/opcache priming and lazy autoloading. -By default, no warm-up is performed. Passing a value less than `1` is normalized to `1`. +Disabled by default. ```php use DragonCode\Benchmark\Benchmark; From f85cdc5430d0128b88ee88c2bf9c1cab902495b4 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 1 May 2026 22:02:51 +0300 Subject: [PATCH 5/5] Fixed merge conflict --- src/Benchmark.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Benchmark.php b/src/Benchmark.php index 7aaf12c..bb76e21 100644 --- a/src/Benchmark.php +++ b/src/Benchmark.php @@ -49,7 +49,6 @@ public static function make(): static /** * Sets the directory for storing benchmark snapshots. * - * @param string $directory * * @return $this */