Skip to content

improve compile perf - #6804

Open
benedikt-bartscher wants to merge 6 commits into
reflex-dev:mainfrom
benedikt-bartscher:improve-compile-perf
Open

improve compile perf#6804
benedikt-bartscher wants to merge 6 commits into
reflex-dev:mainfrom
benedikt-bartscher:improve-compile-perf

Conversation

@benedikt-bartscher

Copy link
Copy Markdown
Contributor

No description provided.

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the node-by-node hasher.update() encoding in _deterministic_hash with a buffer-based approach backed by a per-type encoder dispatch table (_ENCODERS) and an LRU identity cache (_ENCODED_DATACLASSES) for frozen dataclass instances with scalar-only fields, delivering a reported ~3.7× speedup on large page component trees with no change to the resulting hashes.

  • _ENCODERS memoizes the encoder callable per exact Python type, eliminating repeated isinstance chains for hot types such as str, bool, ImportVar, and VarData.
  • _ENCODED_DATACLASSES caches the serialized bytes of qualifying frozen dataclass instances by id, holding a strong reference to prevent id reuse, and evicts oldest-first once it hits the 8 192-entry cap.
  • _deterministic_hash now accepts variadic values and encodes each into a temporary buffer before flushing it into the MD5 hasher, keeping peak memory bounded to a single value's encoding at a time.

Confidence Score: 5/5

Safe to merge; the hash output is unchanged and the new encoding path is well-tested.

The change is a pure performance optimization with no behavioral change to the resulting hashes. The encoder dispatch table and frozen-dataclass identity cache are logically sound, the LRU eviction is correct, and the test suite covers collisions, subclass normalization, cache hits, eviction, and hash stability.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/components/component.py Replaces the incremental hasher-update approach with a buffer-based encoder dispatch table; introduces a frozen-dataclass encoding cache and a type-to-encoder memoization dict. Logic is correct for all tested types; two minor design concerns noted.
tests/units/components/test_component.py Adds comprehensive tests for the new encoding path: collision detection, dict-ordering normalization, subclass normalization, frozen-dataclass caching, LRU eviction, and hash stability across eviction cycles.
packages/reflex-base/news/6804.performance.md Changelog entry accurately describing the ~3.7x speedup and the unchanged hash output guarantee.

Reviews (5): Last reviewed commit: "Merge remote-tracking branch 'upstream/m..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/components/component.py Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 21, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 5.27%

⚡ 3 improved benchmarks
✅ 23 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation test_compile_all_artifacts[_stateful_page] 26.8 ms 25.2 ms +6.17%
Simulation test_compile_page[_stateful_page] 30.3 ms 28.8 ms +5.44%
Simulation test_compile_page_full_context[_stateful_page] 34.8 ms 33.4 ms +4.22%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing benedikt-bartscher:improve-compile-perf (4d83415) with main (7e365ce)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (69ef304) during the generation of this report, so 7e365ce was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review July 21, 2026 11:09
@benedikt-bartscher
benedikt-bartscher requested a review from a team as a code owner July 21, 2026 11:09

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/components/component.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/units/components/test_component.py">

<violation number="1" location="tests/units/components/test_component.py:2475">
P3: This test's name and docstring claim it verifies that the frozen-dataclass encoding cache is reused by identity, but the two equality assertions would pass even if no cache existed at all (equal-but-distinct instances always encode to the same bytes). Either rename/reword it as a plain equality contract test, or actually exercise the cache path, e.g. clear component._ENCODED_DATACLASSES, hash `shared` once, then assert id(shared) is present in the cache and that a second hash of the same object short-circuits.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/components/component.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/components/component.py:688">
P2: The new `_ENCODED_DATACLASSES` cache pins up to 8192 frozen dataclass instances and copies of their encoded bytes in a module-global for the whole process lifetime, released only by a full `clear()` once the cap is hit. Because `_deterministic_hash` runs for every component hash during a compile, these scalar-only frozen instances (and their otherwise-transient byte encodings) no longer get garbage-collected, adding persistent memory that was not retained before. The full-clear-on-capacity eviction also drops the entire cache at once and forces re-encoding of the whole working set right at the boundary, so the cache both holds memory and thrashes. Consider evicting/limiting per-entry (e.g. only cache the bytes keyed by object identity while bounding total retained bytes, or evict entries gradually instead of a wholesale clear).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/components/component.py Outdated
Comment thread tests/units/components/test_component.py
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.

1 participant