Skip to content

Optimize table rendering#19551

Merged
danharrin merged 23 commits into
4.12-betafrom
table-perf
May 6, 2026
Merged

Optimize table rendering#19551
danharrin merged 23 commits into
4.12-betafrom
table-perf

Conversation

@danharrin
Copy link
Copy Markdown
Member

No description provided.

@danharrin danharrin added this to the v4 milestone Mar 23, 2026
@danharrin danharrin added the task label Mar 23, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Roadmap Mar 23, 2026
@Youssef-Amjad
Copy link
Copy Markdown

Are there any details?

@danharrin danharrin changed the base branch from 4.x to 4.12-beta May 3, 2026 17:43
@danharrin
Copy link
Copy Markdown
Member Author

Test file for ref

<?php

use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Schemas\Concerns\InteractsWithSchemas;
use Filament\Schemas\Contracts\HasSchemas;
use Filament\Support\Icons\Heroicon;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Tests\Fixtures\Models\Post;
use Filament\Tests\TestCase;
use Illuminate\Contracts\View\View;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;
use Livewire\Component;

use function Filament\Tests\livewire;

uses(TestCase::class);

it('measures table render performance', function (): void {
    view()->share('errors', (new ViewErrorBag)->put('default', new MessageBag));

    Post::factory()->count(50)->create();

    $iterations = 100;

    $test = livewire(RenderPerformanceTablesComponent::class);

    // Warm up — first renders do eager-loading and cache population
    $test->refresh();
    $test->refresh();

    $start = hrtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        $test->refresh();
    }

    $totalMs = (hrtime(true) - $start) / 1_000_000;
    $avgMs = $totalMs / $iterations;

    echo "\n";
    echo 'Table render performance: ' . number_format($avgMs, 2) . ' ms per render';
    echo " ({$iterations} iterations, 50 rows, 5 TextColumns, 3 row Actions)\n";

    expect(true)->toBeTrue();
});

it('measures table render performance with default action groups', function (): void {
    view()->share('errors', (new ViewErrorBag)->put('default', new MessageBag));

    Post::factory()->count(50)->create();

    $iterations = 100;

    $test = livewire(ActionGroupRenderPerformanceTablesComponent::class);

    $test->refresh();
    $test->refresh();

    $start = hrtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        $test->refresh();
    }

    $totalMs = (hrtime(true) - $start) / 1_000_000;
    $avgMs = $totalMs / $iterations;

    echo "\n";
    echo 'ActionGroup table render performance: ' . number_format($avgMs, 2) . ' ms per render';
    echo " ({$iterations} iterations, 50 rows, 5 TextColumns, 1 ActionGroup with 3 inner Actions per row)\n";

    expect(true)->toBeTrue();
});

class ActionGroupRenderPerformanceTablesComponent extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
{
    use InteractsWithActions;
    use InteractsWithSchemas;
    use Tables\Concerns\InteractsWithTable;

    public function table(Table $table): Table
    {
        return $table
            ->query(Post::query())
            ->columns([
                TextColumn::make('id'),
                TextColumn::make('title'),
                TextColumn::make('content'),
                TextColumn::make('rating'),
                TextColumn::make('is_published'),
            ])
            ->recordActions([
                ActionGroup::make([
                    Action::make('view')->icon(Heroicon::Eye)->url(fn (Post $record): string => "/posts/{$record->id}"),
                    Action::make('edit')->icon(Heroicon::PencilSquare)->url(fn (Post $record): string => "/posts/{$record->id}/edit"),
                    Action::make('delete')->icon(Heroicon::Trash)->color('danger'),
                ]),
            ])
            ->paginated(false);
    }

    public function render(): View
    {
        return view('livewire.table');
    }
}

class RenderPerformanceTablesComponent extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
{
    use InteractsWithActions;
    use InteractsWithSchemas;
    use Tables\Concerns\InteractsWithTable;

    public function table(Table $table): Table
    {
        return $table
            ->query(Post::query())
            ->columns([
                TextColumn::make('id'),
                TextColumn::make('title'),
                TextColumn::make('content'),
                TextColumn::make('rating'),
                TextColumn::make('is_published'),
            ])
            ->recordActions([
                Action::make('view')->icon(Heroicon::Eye)->url(fn (Post $record): string => "/posts/{$record->id}"),
                Action::make('edit')->icon(Heroicon::PencilSquare)->url(fn (Post $record): string => "/posts/{$record->id}/edit"),
                Action::make('delete')->icon(Heroicon::Trash)->color('danger'),
            ])
            ->paginated(false);
    }

    public function render(): View
    {
        return view('livewire.table');
    }
}
  Table render (50 rows · 5 TextColumns · 3 row Actions)

  ┌────────────┬───────┬───────┬───────┬────────────────────────┐
  │   Branch   │ Run 1 │ Run 2 │ Run 3 │         Median         │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ 4.x        │ 30.12 │ 27.89 │ 28.00 │ 28.00 ms               │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ table-perf │ 14.77 │ 14.98 │ 14.49 │ 14.77 ms               │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ Δ          │       │       │       │ −13.23 ms (47% faster) │
  └────────────┴───────┴───────┴───────┴────────────────────────┘

  Table render with ActionGroup (50 rows · 5 TextColumns · 1 ActionGroup wrapping 3 actions)

  ┌────────────┬───────┬───────┬───────┬────────────────────────┐
  │   Branch   │ Run 1 │ Run 2 │ Run 3 │         Median         │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ 4.x        │ 34.00 │ 33.90 │ 33.77 │ 33.90 ms               │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ table-perf │ 18.79 │ 19.38 │ 18.87 │ 18.87 ms               │
  ├────────────┼───────┼───────┼───────┼────────────────────────┤
  │ Δ          │       │       │       │ −15.03 ms (44% faster) │
  └────────────┴───────┴───────┴───────┴────────────────────────

@danharrin danharrin marked this pull request as ready for review May 5, 2026 20:21
@danharrin danharrin merged commit b4aece7 into 4.12-beta May 6, 2026
46 checks passed
@danharrin danharrin deleted the table-perf branch May 6, 2026 20:29
@github-project-automation github-project-automation Bot moved this from Todo to Done in Roadmap May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants