Skip to content

perf(table): memoize dataToRender and $params to cut per-cycle render cost (#19537) - #19603

Open
asrita-pyda wants to merge 1 commit into
primefaces:masterfrom
asrita-pyda:fix/table-render-perf-19537
Open

perf(table): memoize dataToRender and $params to cut per-cycle render cost (#19537)#19603
asrita-pyda wants to merge 1 commit into
primefaces:masterfrom
asrita-pyda:fix/table-render-perf-19537

Conversation

@asrita-pyda

@asrita-pyda asrita-pyda commented May 29, 2026

Copy link
Copy Markdown

Closes #19537

Defining the issue

Table rendering latency regressed for large datasets (#19537). Profiling the change-detection hot
path surfaced two pieces of redundant work that run on every CD cycle, because both Table and
the per-row TableBody use ChangeDetectionStrategy.Default:

  1. dataToRender() re-allocates the row array every cycle. The body binds
    [value]="dataToRender(scrollerOptions.rows)" — a method binding. With a paginator it returned a
    fresh .slice() on every cycle, which re-fired the TableBody.value setter (a forced layout
    reflow getOuterHeight() for frozen rows) and handed *ngFor a new collection reference to
    re-diff.
  2. BaseComponent.$params is recomputed per element per cycle. cx()/sx()/ptm() (and the
    $pt/$style/$globalPT/$defaultPT getters) read $params, which walked the
    $parentInstance chain via _getHostInstance and allocated two objects on every call. For a
    row directive such as pSelectableRow ([class]="cx('selectableRow')") this ran once per row,
    per cycle.

Fix

  • dataToRender() (table.ts) now memoizes its result, returning a referentially stable array
    until its inputs change. Cache key: resolved data reference, length, effective first, rows,
    lazy, paginator. Sort reassigns _value = [...] and filter reassigns filteredValue, so any
    data change flips the reference and invalidates the cache automatically. Non-array arguments
    bypass the cache, preserving the original behavior byte-for-byte.
  • BaseComponent.$params (basecomponent.ts) is computed once per instance and cached. Its
    inputs ($parentInstance, $name, $hostName) are immutable after construction, and instance
    remains the live this, so style/PT functions still read live state. The cache field is declared
    before parent = this.$params.parent so ES2022 define-semantics field init does not reset a cache
    populated during construction.

No public API changes and no change-detection-strategy changes.

Tests

Added deterministic, referential-stability tests (no wall-clock benchmarks) to table.spec.ts:
dataToRender memoization (stable reference, no per-cycle allocation, correct re-slice on
first/rows change, sort/filter cache busting, non-array bypass) and $params memoization
(stable reference, instance === component).

Validated against the suites that most exercise PassThrough + the _getHostInstance parent-chain
walk:

Suite Result
table.spec.ts (incl. 23 PassThrough) 73 passing
treetable.spec.ts 174 passing
stepper.spec.ts 58 passing
tabs.spec.ts 73 passing

All green; no functional regressions in sorting, filtering, pagination, selection, virtual scroll,
templates, or PassThrough.

🤖 Generated with Claude Code

… cost (primefaces#19537)

Table render latency regressed for large datasets. Two hot paths ran redundant work on every change-detection cycle:

- The body binding [value]="dataToRender(...)" re-sliced the data array every CD cycle when paginated, re-firing the TableBody value setter (a forced layout reflow for frozen rows) and churning *ngFor. dataToRender now memoizes its result, returning a referentially stable array until its inputs (data ref, length, first, rows, lazy, paginator) change. Non-array args bypass the cache, preserving exact prior behavior.

- BaseComponent.$params, read by cx()/sx()/ptm() per element per cycle, walked the parent-instance chain and allocated two objects on every call. It is now computed once per instance and cached; 'instance' stays the live 'this' so style/PT functions still read live state.

No public API or change-detection-strategy changes. Adds deterministic referential-stability tests. Validated: table 73, treetable 174, stepper 58, tabs 73 - all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@asrita-pyda

Copy link
Copy Markdown
Author

This PR addresses #19537 — it resolves the Table rendering-latency regression described there. See the PR description for the profiling findings (per-cycle dataToRender re-allocation and per-row $params recomputation) and the memoization fix.

@asrita-pyda

Copy link
Copy Markdown
Author

@mehmetcetin01140 @cagataycivici would you mind reviewing when you have a moment? This is a focused, API-compatible performance fix for the Table render regression in #19537 (memoizes dataToRender and BaseComponent.$params), with tests passing across table/treetable/stepper/tabs. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Table : rendering performance regression

1 participant