Low priority, and it should not be merged without a benchmark showing it actually pays — see "Must be benchmarked" below.
Describe the Enhancement you want
RadixThemesComponent._get_app_wrap_components builds a new provider on every call:
# packages/reflex-components-radix/src/reflex_components_radix/themes/base.py#L131-L134
@staticmethod
def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
return {
(45, "RadixThemesColorModeProvider"): RadixThemesColorModeProvider.create(),
}
Every Radix component in the tree contributes one, and they are all equivalent — the dict is keyed (45, "RadixThemesColorModeProvider"), so _get_all_app_wrap_components collapses them and all but one is discarded. The rest are constructed only to be thrown away, and anything that walks or renders them in between pays for each one separately.
The enhancement is to return a memoized instance rather than a fresh one per call.
What this costs today
Profiling a memoization-heavy page (a synthetic one built for this, not a repo benchmark), during a single page compile:
|
|
RadixThemesColorModeProvider values reaching the memo-name hash |
114 |
| distinct object identities among them |
113 |
| time spent encoding them |
6.58 ms of 22.1 ms total encoding (~30%) |
encoder share of component_hash |
~13–14% |
component_hash share of the compile |
~45% |
So the encoder-side cost alone is roughly 1.6% of that compile. Because the instances are distinct objects with identity-based equality, no cache in the hash can collapse them — the duplication has to be removed where they are created.
What has not been measured
Only the encoding cost above was measured. The construction cost of the ~113 discarded instances, and whatever they add to the _get_all_app_wrap_components walk, are unmeasured and plausibly larger than the encoding. That is the main thing a benchmark needs to settle.
Must be benchmarked
The numbers above come from one synthetic page on one machine. Before this is worth taking:
- show the improvement on
tests/benchmarks/test_compilation.py (codspeed), not just a local script
- confirm it holds on a page with many Radix components, which is where the duplication concentrates
- if the win is inside noise, close this — the change is not worth the risk on its own
Correctness to check first
Sharing one instance is not automatically safe. The compiler mutates component trees in place (for example when inserting memo wrappers), so a module-level singleton could be mutated by one page's compile and observed by the next. Anything done here needs to establish that the shared provider is never mutated, or hand out a copy at the point where mutation could happen. A per-compile cache may be a better fit than a process-lifetime one.
Additional context
Found while profiling the memo-name hash in #6947. That PR does not touch this; the hash is only what made the duplication visible.
A different angle on the same waste, if the memoization turns out to be awkward: hash app-wrap components by their (priority, tag) key plus class rather than by rendering them, since the key already identifies the wrapper uniquely. That helps the hash only, not the construction cost, so it is the weaker of the two.
Low priority, and it should not be merged without a benchmark showing it actually pays — see "Must be benchmarked" below.
Describe the Enhancement you want
RadixThemesComponent._get_app_wrap_componentsbuilds a new provider on every call:Every Radix component in the tree contributes one, and they are all equivalent — the dict is keyed
(45, "RadixThemesColorModeProvider"), so_get_all_app_wrap_componentscollapses them and all but one is discarded. The rest are constructed only to be thrown away, and anything that walks or renders them in between pays for each one separately.The enhancement is to return a memoized instance rather than a fresh one per call.
What this costs today
Profiling a memoization-heavy page (a synthetic one built for this, not a repo benchmark), during a single page compile:
RadixThemesColorModeProvidervalues reaching the memo-name hashcomponent_hashcomponent_hashshare of the compileSo the encoder-side cost alone is roughly 1.6% of that compile. Because the instances are distinct objects with identity-based equality, no cache in the hash can collapse them — the duplication has to be removed where they are created.
What has not been measured
Only the encoding cost above was measured. The construction cost of the ~113 discarded instances, and whatever they add to the
_get_all_app_wrap_componentswalk, are unmeasured and plausibly larger than the encoding. That is the main thing a benchmark needs to settle.Must be benchmarked
The numbers above come from one synthetic page on one machine. Before this is worth taking:
tests/benchmarks/test_compilation.py(codspeed), not just a local scriptCorrectness to check first
Sharing one instance is not automatically safe. The compiler mutates component trees in place (for example when inserting memo wrappers), so a module-level singleton could be mutated by one page's compile and observed by the next. Anything done here needs to establish that the shared provider is never mutated, or hand out a copy at the point where mutation could happen. A per-compile cache may be a better fit than a process-lifetime one.
Additional context
Found while profiling the memo-name hash in #6947. That PR does not touch this; the hash is only what made the duplication visible.
A different angle on the same waste, if the memoization turns out to be awkward: hash app-wrap components by their
(priority, tag)key plus class rather than by rendering them, since the key already identifies the wrapper uniquely. That helps the hash only, not the construction cost, so it is the weaker of the two.