diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml
index 02766190..9fde47f9 100644
--- a/.github/workflows/benchmark-comment.yml
+++ b/.github/workflows/benchmark-comment.yml
@@ -30,7 +30,7 @@ jobs:
echo "heading=$(sed -n '2p' benchmark-result.md)" >> "$GITHUB_OUTPUT"
tail -n +2 benchmark-result.md > benchmark-comment.md
echo "" >> benchmark-comment.md
- echo "To run a specific benchmark, comment /benchmark <name> where name is one of: attributes, aware, class, default, forwarding, merge, named-slots, no-attributes, slot" >> benchmark-comment.md
+ echo "To run a specific benchmark, comment /benchmark <name>
attributes, aware, class, default, forwarding, merge, named-slots, no-attributes, slot, compilation" >> benchmark-comment.md
- name: Find existing comment
uses: peter-evans/find-comment@v3
diff --git a/.github/workflows/benchmark-on-demand.yml b/.github/workflows/benchmark-on-demand.yml
index 83b62b59..fa776c79 100644
--- a/.github/workflows/benchmark-on-demand.yml
+++ b/.github/workflows/benchmark-on-demand.yml
@@ -18,7 +18,7 @@ jobs:
COMMENT: ${{ github.event.comment.body }}
run: |
BENCHMARK=$(echo "$COMMENT" | awk '{print $2}')
- VALID="attributes aware class default forwarding merge named-slots no-attributes slot"
+ VALID="attributes aware class compilation default forwarding merge named-slots no-attributes slot"
if ! echo "$VALID" | grep -qw "$BENCHMARK"; then
echo "::error::Unknown benchmark '$BENCHMARK'. Valid options: $VALID"
exit 1
diff --git a/workbench/app/Console/Commands/BenchmarkCommand.php b/workbench/app/Console/Commands/BenchmarkCommand.php
index 930637a3..74d51ace 100644
--- a/workbench/app/Console/Commands/BenchmarkCommand.php
+++ b/workbench/app/Console/Commands/BenchmarkCommand.php
@@ -9,6 +9,7 @@
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
+use Livewire\Blaze\Blaze;
class BenchmarkCommand extends Command
{
@@ -77,6 +78,11 @@ public function handle(): int
protected function runBenchmark(): array
{
$benchmarkName = $this->argument('benchmark');
+
+ if ($benchmarkName === 'compilation') {
+ return $this->runCompilationBenchmark();
+ }
+
$bladeView = "bench.blade.{$benchmarkName}";
$blazeView = "bench.blaze.{$benchmarkName}";
$showProgress = ! $this->option('ci') && ! $this->option('json');
@@ -144,6 +150,37 @@ protected function runBenchmark(): array
];
}
+ protected function runCompilationBenchmark(): array
+ {
+ for ($w = 0; $w < $this->warmupRounds; $w++) {
+ $this->measureCompilation(false);
+ $this->measureCompilation(true);
+ }
+
+ $bladeTimes = [];
+ $blazeTimes = [];
+
+ for ($r = 0; $r < $this->rounds; $r++) {
+ if ($r % 2 === 0) {
+ $bladeTimes[] = $this->measureCompilation(false);
+ $blazeTimes[] = $this->measureCompilation(true);
+ } else {
+ $blazeTimes[] = $this->measureCompilation(true);
+ $bladeTimes[] = $this->measureCompilation(false);
+ }
+ }
+
+ $keptRounds = $this->nonOutlierIndices(collect($bladeTimes))
+ ->intersect($this->nonOutlierIndices(collect($blazeTimes)))
+ ->values();
+ $this->filteredRounds = $this->rounds - $keptRounds->count();
+
+ return [
+ 'blade_ms' => round($keptRounds->map(fn ($r) => $bladeTimes[$r])->median(), 2),
+ 'blaze_ms' => round($keptRounds->map(fn ($r) => $blazeTimes[$r])->median(), 2),
+ ];
+ }
+
protected function runMultipleAttempts(int $attempts): int
{
$benchmarkName = $this->argument('benchmark');
@@ -246,20 +283,20 @@ protected function buildTable(array $results): array
{
$snapshot = $this->option('snapshot') ? null : $this->loadSnapshot();
- $headers = ['Blade', 'Blaze', 'Improvement'];
+ $headers = ['Blade', 'Blaze', 'Change'];
$rows = collect($results)->map(function ($result, $name) use ($snapshot) {
$blade = $this->formatTime($result['blade_ms']);
$blaze = $this->formatTime($result['blaze_ms']);
- $improvement = $this->improvement($result).'%';
+ $change = $this->change($result).'%';
if ($prev = $snapshot['benchmarks'][$name] ?? null) {
$blade .= ' '.$this->formatChange($prev['blade_ms'], $result['blade_ms']);
$blaze .= ' '.$this->formatChange($prev['blaze_ms'], $result['blaze_ms']);
- $improvement .= ' '.$this->formatImprovementChange($prev['improvement'], $this->improvement($result));
+ $change .= ' '.$this->formatChangeDelta($prev['change'], $this->change($result));
}
- return [$blade, $blaze, $improvement];
+ return [$blade, $blaze, $change];
})->values()->all();
return [$headers, $rows, $snapshot];
@@ -333,13 +370,13 @@ protected function displayAttemptsResults(array $allAttempts, array $results, Co
foreach ($allAttempts as $i => $attempt) {
$isOutlier = ! $keptIndices->contains($i);
- $improvement = $this->improvement($attempt);
+ $change = $this->change($attempt);
$line = sprintf(
' Attempt %d: Blade %s Blaze %s (%s%%)',
$i + 1,
$this->formatTime($attempt['blade_ms']),
$this->formatTime($attempt['blaze_ms']),
- $improvement
+ $change
);
$isOutlier
@@ -374,7 +411,7 @@ protected function outputMarkdownAttempts(array $allAttempts, array $results, Co
$snapshot = $this->option('snapshot') ? null : $this->loadSnapshot();
$snapshotData = $snapshot['benchmarks'][$benchmarkName] ?? null;
- $headers = ['Attempt', 'Blade', 'Blaze', 'Improvement'];
+ $headers = ['Attempt', 'Blade', 'Blaze', 'Change'];
$rows = [];
@@ -386,7 +423,7 @@ protected function outputMarkdownAttempts(array $allAttempts, array $results, Co
'`#'.($i + 1).'`'.($isOutlier ? ' \*' : ''),
$this->formatTime($attempt['blade_ms']),
$this->formatTime($attempt['blaze_ms']),
- $this->improvement($attempt).'%',
+ $this->change($attempt).'%',
];
}
@@ -396,22 +433,22 @@ protected function outputMarkdownAttempts(array $allAttempts, array $results, Co
'Snapshot',
$this->formatTime($snapshotData['blade_ms']),
$this->formatTime($snapshotData['blaze_ms']),
- $snapshotData['improvement'].'%',
+ $snapshotData['change'].'%',
];
}
// Result row (median with comparison deltas when snapshot exists).
$blade = $this->formatTime($medianResult['blade_ms']);
$blaze = $this->formatTime($medianResult['blaze_ms']);
- $improvement = $this->improvement($medianResult).'%';
+ $change = $this->change($medianResult).'%';
if ($snapshotData) {
$blade .= ' '.$this->formatChange($snapshotData['blade_ms'], $medianResult['blade_ms']);
$blaze .= ' '.$this->formatChange($snapshotData['blaze_ms'], $medianResult['blaze_ms']);
- $improvement .= ' '.$this->formatImprovementChange($snapshotData['improvement'], $this->improvement($medianResult));
+ $change .= ' '.$this->formatChangeDelta($snapshotData['change'], $this->change($medianResult));
}
- $rows[] = ['**Result**', "**{$blade}**", "**{$blaze}**", "**{$improvement}**"];
+ $rows[] = ['**Result**', "**{$blade}**", "**{$blaze}**", "**{$change}**"];
$allRows = collect([$headers, ...$rows]);
$widths = collect($headers)->keys()->map(
@@ -456,7 +493,7 @@ protected function outputJsonAttemptsResults(string $benchmarkName, array $allAt
'attempts_detail' => collect($allAttempts)->map(fn ($attempt, $i) => [
'blade_ms' => $attempt['blade_ms'],
'blaze_ms' => $attempt['blaze_ms'],
- 'improvement' => $this->improvement($attempt),
+ 'change' => $this->change($attempt),
'outlier' => ! $keptIndices->contains($i),
])->values()->all(),
'benchmarks' => $results,
@@ -471,7 +508,7 @@ protected function saveSnapshot(array $results): void
'benchmarks' => collect($results)->map(fn ($result) => [
'blade_ms' => $result['blade_ms'],
'blaze_ms' => $result['blaze_ms'],
- 'improvement' => $this->improvement($result),
+ 'change' => $this->change($result),
])->all(),
];
@@ -501,7 +538,7 @@ protected function snapshotPath(): string
return dirname(__DIR__, 4).'/benchmark-snapshot.json';
}
- protected function improvement(array $result): float
+ protected function change(array $result): float
{
return $result['blade_ms'] > 0
? round((1 - $result['blaze_ms'] / $result['blade_ms']) * 100, 1)
@@ -525,7 +562,7 @@ protected function formatChange(float $old, float $new, float $threshold = 2): s
return "({$sign}".round($change, 1).'%)';
}
- protected function formatImprovementChange(float $old, float $new, float $threshold = 0.2): string
+ protected function formatChangeDelta(float $old, float $new, float $threshold = 0.2): string
{
$delta = round($new - $old, 1);
@@ -577,6 +614,28 @@ protected function measureView(string $view): float
return (hrtime(true) - $start) / 1_000_000;
}
+ protected function measureCompilation(bool $blaze): float
+ {
+ $blaze ? Blaze::enable() : Blaze::disable();
+
+ Artisan::call('view:clear');
+
+ $compiler = app('blade.compiler');
+ $views = File::allFiles(resource_path('views'));
+
+ gc_collect_cycles();
+
+ $start = hrtime(true);
+
+ foreach ($views as $view) {
+ if ($view->getExtension() === 'php' && str_ends_with($view->getFilename(), '.blade.php')) {
+ $compiler->compile($view->getPathname());
+ }
+ }
+
+ return (hrtime(true) - $start) / 1_000_000;
+ }
+
protected function formatTime(float $ms): string
{
return number_format($ms, 2).'ms';
diff --git a/workbench/app/Console/Commands/BenchmarkVarianceCommand.php b/workbench/app/Console/Commands/BenchmarkVarianceCommand.php
index 9bbcb458..b59aa5e0 100644
--- a/workbench/app/Console/Commands/BenchmarkVarianceCommand.php
+++ b/workbench/app/Console/Commands/BenchmarkVarianceCommand.php
@@ -128,31 +128,31 @@ protected function runBenchmarkInProcess(string $name): array
protected function displayVarianceResults(array $snapshot, array $allRuns, float $avgRunDuration, float $totalDuration): void
{
- $snapshotImprovement = $this->improvement($snapshot);
+ $snapshotChange = $this->change($snapshot);
$bladeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blade_ms'], $run['blade_ms']));
$blazeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blaze_ms'], $run['blaze_ms']));
- $improvementChanges = collect($allRuns)->map(fn ($run) => round($this->improvement($run) - $snapshotImprovement, 1));
+ $changes = collect($allRuns)->map(fn ($run) => round($this->change($run) - $snapshotChange, 1));
- $headers = ['', 'Blade', 'Blaze', 'Improvement'];
+ $headers = ['', 'Blade', 'Blaze', 'Change'];
$rows = [
[
'Snapshot',
$this->formatTime($snapshot['blade_ms']),
$this->formatTime($snapshot['blaze_ms']),
- $snapshotImprovement.'%',
+ $snapshotChange.'%',
],
[
'Variance',
$this->formatVarianceRange($bladeChanges->min(), $bladeChanges->max()),
$this->formatVarianceRange($blazeChanges->min(), $blazeChanges->max()),
- $this->formatVarianceRange($improvementChanges->min(), $improvementChanges->max()),
+ $this->formatVarianceRange($changes->min(), $changes->max()),
],
[
'Std Dev',
'±'.$this->stddev($bladeChanges).'%',
'±'.$this->stddev($blazeChanges).'%',
- '±'.$this->stddev($improvementChanges).'%',
+ '±'.$this->stddev($changes).'%',
],
];
@@ -168,31 +168,31 @@ protected function displayVarianceResults(array $snapshot, array $allRuns, float
protected function outputVarianceMarkdown(array $snapshot, array $allRuns, float $avgRunDuration, float $totalDuration): void
{
- $snapshotImprovement = $this->improvement($snapshot);
+ $snapshotChange = $this->change($snapshot);
$bladeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blade_ms'], $run['blade_ms']));
$blazeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blaze_ms'], $run['blaze_ms']));
- $improvementChanges = collect($allRuns)->map(fn ($run) => round($this->improvement($run) - $snapshotImprovement, 1));
+ $changes = collect($allRuns)->map(fn ($run) => round($this->change($run) - $snapshotChange, 1));
- $headers = ['', 'Blade', 'Blaze', 'Improvement'];
+ $headers = ['', 'Blade', 'Blaze', 'Change'];
$rows = [
[
'Snapshot',
$this->formatTime($snapshot['blade_ms']),
$this->formatTime($snapshot['blaze_ms']),
- $snapshotImprovement.'%',
+ $snapshotChange.'%',
],
[
'Variance',
$this->formatVarianceRange($bladeChanges->min(), $bladeChanges->max()),
$this->formatVarianceRange($blazeChanges->min(), $blazeChanges->max()),
- $this->formatVarianceRange($improvementChanges->min(), $improvementChanges->max()),
+ $this->formatVarianceRange($changes->min(), $changes->max()),
],
[
'Std Dev',
'±'.$this->stddev($bladeChanges).'%',
'±'.$this->stddev($blazeChanges).'%',
- '±'.$this->stddev($improvementChanges).'%',
+ '±'.$this->stddev($changes).'%',
],
];
@@ -230,7 +230,7 @@ protected function saveSnapshot(array $results): void
'benchmarks' => collect($results)->map(fn ($result) => [
'blade_ms' => $result['blade_ms'],
'blaze_ms' => $result['blaze_ms'],
- 'improvement' => $this->improvement($result),
+ 'change' => $this->change($result),
])->all(),
];
@@ -242,11 +242,11 @@ protected function saveSnapshot(array $results): void
protected function outputJson(array $snapshot, array $allRuns, float $avgRunDuration, float $totalDuration): void
{
- $snapshotImprovement = $this->improvement($snapshot);
+ $snapshotChange = $this->change($snapshot);
$bladeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blade_ms'], $run['blade_ms']));
$blazeChanges = collect($allRuns)->map(fn ($run) => $this->percentChange($snapshot['blaze_ms'], $run['blaze_ms']));
- $improvementChanges = collect($allRuns)->map(fn ($run) => round($this->improvement($run) - $snapshotImprovement, 1));
+ $changes = collect($allRuns)->map(fn ($run) => round($this->change($run) - $snapshotChange, 1));
$this->output->writeln(json_encode([
'iterations' => $this->iterations,
@@ -258,12 +258,12 @@ protected function outputJson(array $snapshot, array $allRuns, float $avgRunDura
'snapshot' => [
'blade_ms' => $snapshot['blade_ms'],
'blaze_ms' => $snapshot['blaze_ms'],
- 'improvement' => $snapshotImprovement,
+ 'change' => $snapshotChange,
],
'variance' => [
'blade' => ['min' => $bladeChanges->min(), 'max' => $bladeChanges->max(), 'stddev' => $this->stddev($bladeChanges)],
'blaze' => ['min' => $blazeChanges->min(), 'max' => $blazeChanges->max(), 'stddev' => $this->stddev($blazeChanges)],
- 'improvement' => ['min' => $improvementChanges->min(), 'max' => $improvementChanges->max(), 'stddev' => $this->stddev($improvementChanges)],
+ 'change' => ['min' => $changes->min(), 'max' => $changes->max(), 'stddev' => $this->stddev($changes)],
],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}