From 64d97999ec6e0385d317dd15274fef62f08fdb50 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 19:50:37 +0000 Subject: [PATCH 1/4] Dedup ImportVar entries in merge_imports / merge_parsed_imports merge_imports extended per-lib lists without dedup. Every @var_operation merges the same operand VarData through both _args and _return, so chained operations (rx.cond chains, arithmetic) doubled the merged import entries per nesting level: depth 15 produced ~196k entries (29ms per merge), depth 18 ~1.57M (252ms). ImportVar is a frozen (hashable) dataclass, so both merge functions now accumulate per-lib insertion-ordered dicts, dropping exact duplicates while preserving first-seen order and the distinctness of entries that differ in alias/default/etc. This keeps merged VarData constant-sized for chained operations and linear for component trees. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g --- .../src/reflex_base/utils/imports.py | 49 ++++++++++++++----- tests/units/utils/test_imports.py | 34 +++++++++++++ tests/units/vars/test_base.py | 26 +++++++++- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/utils/imports.py b/packages/reflex-base/src/reflex_base/utils/imports.py index 7e614f99524..636e648ef89 100644 --- a/packages/reflex-base/src/reflex_base/utils/imports.py +++ b/packages/reflex-base/src/reflex_base/utils/imports.py @@ -17,22 +17,45 @@ ) +def _as_import_lists( + deduped: defaultdict[str, dict[ImportVar, None]], +) -> ParsedImportDict: + """Convert per-lib dedup dicts into the list-valued merge result. + + Args: + deduped: The merged imports with per-lib insertion-ordered dedup dicts. + + Returns: + The merged import dict with list values. + """ + merged: defaultdict[str, list[ImportVar]] = defaultdict(list) + for lib, fields in deduped.items(): + merged[lib] = list(fields) + return merged + + def merge_parsed_imports( *imports: ImmutableParsedImportDict, ) -> ParsedImportDict: """Merge multiple parsed import dicts together. + Duplicate ``ImportVar`` entries are dropped (keeping first-seen order) so + repeated merges stay linear instead of growing with every level of + nesting. + Args: *imports: The list of import dicts to merge. Returns: The merged import dicts. """ - all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) + all_imports: defaultdict[str, dict[ImportVar, None]] = defaultdict(dict) for import_dict in imports: for lib, fields in import_dict.items(): - all_imports[lib].extend(fields) - return all_imports + bucket = all_imports[lib] + for field in fields: + bucket[field] = None + return _as_import_lists(all_imports) def merge_imports( @@ -40,29 +63,31 @@ def merge_imports( ) -> ParsedImportDict: """Merge multiple import dicts together. + Duplicate ``ImportVar`` entries are dropped (keeping first-seen order) so + repeated merges stay linear instead of growing with every level of + nesting — e.g. chained var operations merge the same operand imports via + both ``_args`` and ``_return``. + Args: *imports: The list of import dicts to merge. Returns: The merged import dicts. """ - all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) + all_imports: defaultdict[str, dict[ImportVar, None]] = defaultdict(dict) for import_dict in imports: for lib, fields in ( import_dict if isinstance(import_dict, tuple) else import_dict.items() ): # If the lib is an absolute path, we need to prefix it with a $ lib = "$" + lib if lib.startswith(ABSOLUTE_IMPORT_PREFIXES) else lib + bucket = all_imports[lib] if isinstance(fields, (list, tuple, set)): - all_imports[lib].extend( - ImportVar(field) if isinstance(field, str) else field - for field in fields - ) + for field in fields: + bucket[ImportVar(field) if isinstance(field, str) else field] = None else: - all_imports[lib].append( - ImportVar(fields) if isinstance(fields, str) else fields - ) - return all_imports + bucket[ImportVar(fields) if isinstance(fields, str) else fields] = None + return _as_import_lists(all_imports) def parse_imports( diff --git a/tests/units/utils/test_imports.py b/tests/units/utils/test_imports.py index 7b456d892c9..772e0248911 100644 --- a/tests/units/utils/test_imports.py +++ b/tests/units/utils/test_imports.py @@ -4,6 +4,7 @@ ImportVar, ParsedImportDict, merge_imports, + merge_parsed_imports, parse_imports, ) @@ -91,6 +92,39 @@ def test_merge_imports(input_1, input_2, output): assert set(res[key]) == set(output[key]) +def test_merge_imports_dedups_duplicates(): + """Duplicate ImportVars collapse to one entry, preserving first-seen order. + + Regression: without dedup, chained var operations (which merge the same + operand imports via both ``_args`` and ``_return``) doubled the entry + count per nesting level. + """ + merged = merge_imports( + {"react": ["useEffect", "useState", "useEffect"]}, + {"react": ["useState", "useMemo"]}, + ) + assert merged["react"] == [ + ImportVar(tag="useEffect"), + ImportVar(tag="useState"), + ImportVar(tag="useMemo"), + ] + + +def test_merge_parsed_imports_dedups_duplicates(): + """merge_parsed_imports drops exact duplicates but keeps distinct fields.""" + parsed = {"react": [ImportVar(tag="useEffect"), ImportVar(tag="useState")]} + merged = merge_parsed_imports(parsed, parsed) + assert merged["react"] == [ImportVar(tag="useEffect"), ImportVar(tag="useState")] + + aliased = {"react": [ImportVar(tag="useEffect", alias="uE")]} + merged = merge_parsed_imports(parsed, aliased) + assert merged["react"] == [ + ImportVar(tag="useEffect"), + ImportVar(tag="useState"), + ImportVar(tag="useEffect", alias="uE"), + ] + + @pytest.mark.parametrize( ("input", "output"), [ diff --git a/tests/units/vars/test_base.py b/tests/units/vars/test_base.py index a8075357fa7..4ed3a77535d 100644 --- a/tests/units/vars/test_base.py +++ b/tests/units/vars/test_base.py @@ -1,7 +1,8 @@ from collections.abc import Mapping, Sequence import pytest -from reflex_base.vars.base import computed_var, figure_out_type +from reflex_base.utils.imports import ImportVar +from reflex_base.vars.base import Var, VarData, computed_var, figure_out_type from reflex.state import State @@ -42,6 +43,29 @@ def test_figure_out_type(value, expected): assert figure_out_type(value) == expected +def test_chained_var_operations_var_data_stays_bounded() -> None: + """Deeply chained var operations keep merged VarData entries deduped. + + Regression: every ``@var_operation`` merges the same operand VarData via + both ``_args`` and ``_return``, so without dedup in ``merge_imports`` the + merged import entries doubled per nesting level (depth 18 produced ~1.57M + entries and a multi-hundred-millisecond merge). + """ + base = Var( + _js_expr="a", + _var_type=int, + _var_data=VarData(imports={"my-lib": [ImportVar(tag="thing")]}), + ).guess_type() + + chained = base + for i in range(18): + chained = chained + i + + var_data = chained._get_all_var_data() + assert var_data is not None + assert dict(var_data.imports)["my-lib"] == (ImportVar(tag="thing"),) + + def test_computed_var_replace() -> None: class StateTest(State): @computed_var(cache=True) From de51c40e9ed95a5ff5cf19f2035c0976135188c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 19:58:56 +0000 Subject: [PATCH 2/4] Keep merge_parsed_imports as plain concat; dedup only in merge_imports CodSpeed flagged test_get_all_imports regressing ~40% with dedup in the component-tree fold: per-entry Python-level hashing outweighs list extend there, duplication is only linear, and the result is collapsed downstream anyway. The compounding-duplication path (VarData.merge for chained var operations) goes through merge_imports, which keeps the dedup. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g --- .../src/reflex_base/utils/imports.py | 44 +++++++------------ tests/units/utils/test_imports.py | 16 ++++--- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/utils/imports.py b/packages/reflex-base/src/reflex_base/utils/imports.py index 636e648ef89..405c07c8ca7 100644 --- a/packages/reflex-base/src/reflex_base/utils/imports.py +++ b/packages/reflex-base/src/reflex_base/utils/imports.py @@ -17,31 +17,16 @@ ) -def _as_import_lists( - deduped: defaultdict[str, dict[ImportVar, None]], -) -> ParsedImportDict: - """Convert per-lib dedup dicts into the list-valued merge result. - - Args: - deduped: The merged imports with per-lib insertion-ordered dedup dicts. - - Returns: - The merged import dict with list values. - """ - merged: defaultdict[str, list[ImportVar]] = defaultdict(list) - for lib, fields in deduped.items(): - merged[lib] = list(fields) - return merged - - def merge_parsed_imports( *imports: ImmutableParsedImportDict, ) -> ParsedImportDict: """Merge multiple parsed import dicts together. - Duplicate ``ImportVar`` entries are dropped (keeping first-seen order) so - repeated merges stay linear instead of growing with every level of - nesting. + Entries are concatenated without dedup: this is the component-tree fold + (``_get_all_imports``), where duplication is linear and the result is + collapsed downstream. Per-entry hashing here measurably slows the walk; + the compounding-duplication path (``VarData.merge``) goes through + :func:`merge_imports`, which does dedup. Args: *imports: The list of import dicts to merge. @@ -49,13 +34,11 @@ def merge_parsed_imports( Returns: The merged import dicts. """ - all_imports: defaultdict[str, dict[ImportVar, None]] = defaultdict(dict) + all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list) for import_dict in imports: for lib, fields in import_dict.items(): - bucket = all_imports[lib] - for field in fields: - bucket[field] = None - return _as_import_lists(all_imports) + all_imports[lib].extend(fields) + return all_imports def merge_imports( @@ -64,9 +47,9 @@ def merge_imports( """Merge multiple import dicts together. Duplicate ``ImportVar`` entries are dropped (keeping first-seen order) so - repeated merges stay linear instead of growing with every level of - nesting — e.g. chained var operations merge the same operand imports via - both ``_args`` and ``_return``. + repeated merges stay constant-sized instead of growing with every level + of nesting — e.g. chained var operations merge the same operand imports + via both ``_args`` and ``_return``, which doubled entries per level. Args: *imports: The list of import dicts to merge. @@ -87,7 +70,10 @@ def merge_imports( bucket[ImportVar(field) if isinstance(field, str) else field] = None else: bucket[ImportVar(fields) if isinstance(fields, str) else fields] = None - return _as_import_lists(all_imports) + merged: defaultdict[str, list[ImportVar]] = defaultdict(list) + for lib, bucket in all_imports.items(): + merged[lib] = list(bucket) + return merged def parse_imports( diff --git a/tests/units/utils/test_imports.py b/tests/units/utils/test_imports.py index 772e0248911..127c992dd18 100644 --- a/tests/units/utils/test_imports.py +++ b/tests/units/utils/test_imports.py @@ -110,14 +110,11 @@ def test_merge_imports_dedups_duplicates(): ] -def test_merge_parsed_imports_dedups_duplicates(): - """merge_parsed_imports drops exact duplicates but keeps distinct fields.""" +def test_merge_imports_keeps_distinct_fields(): + """Entries differing in any field (e.g. alias) are not collapsed.""" parsed = {"react": [ImportVar(tag="useEffect"), ImportVar(tag="useState")]} - merged = merge_parsed_imports(parsed, parsed) - assert merged["react"] == [ImportVar(tag="useEffect"), ImportVar(tag="useState")] - aliased = {"react": [ImportVar(tag="useEffect", alias="uE")]} - merged = merge_parsed_imports(parsed, aliased) + merged = merge_imports(parsed, aliased) assert merged["react"] == [ ImportVar(tag="useEffect"), ImportVar(tag="useState"), @@ -125,6 +122,13 @@ def test_merge_parsed_imports_dedups_duplicates(): ] +def test_merge_parsed_imports_concatenates(): + """The component-tree fold concatenates without dedup (collapsed later).""" + parsed = {"react": [ImportVar(tag="useEffect")]} + merged = merge_parsed_imports(parsed, parsed) + assert merged["react"] == [ImportVar(tag="useEffect"), ImportVar(tag="useEffect")] + + @pytest.mark.parametrize( ("input", "output"), [ From 0e6a96bc54d0def004c57edf45f664ac680278c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 20:01:10 +0000 Subject: [PATCH 3/4] Add news fragment and fix codespell-flagged alias in test Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g --- packages/reflex-base/news/6737.performance.md | 1 + tests/units/utils/test_imports.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 packages/reflex-base/news/6737.performance.md diff --git a/packages/reflex-base/news/6737.performance.md b/packages/reflex-base/news/6737.performance.md new file mode 100644 index 00000000000..016372c05bc --- /dev/null +++ b/packages/reflex-base/news/6737.performance.md @@ -0,0 +1 @@ +Dedup `ImportVar` entries in `merge_imports`, keeping merged `VarData` constant-sized for chained var operations (previously entries doubled per nesting level: depth 18 produced ~1.57M entries and a multi-hundred-millisecond merge). diff --git a/tests/units/utils/test_imports.py b/tests/units/utils/test_imports.py index 127c992dd18..b372f7c3ac2 100644 --- a/tests/units/utils/test_imports.py +++ b/tests/units/utils/test_imports.py @@ -113,12 +113,12 @@ def test_merge_imports_dedups_duplicates(): def test_merge_imports_keeps_distinct_fields(): """Entries differing in any field (e.g. alias) are not collapsed.""" parsed = {"react": [ImportVar(tag="useEffect"), ImportVar(tag="useState")]} - aliased = {"react": [ImportVar(tag="useEffect", alias="uE")]} + aliased = {"react": [ImportVar(tag="useEffect", alias="effectAlias")]} merged = merge_imports(parsed, aliased) assert merged["react"] == [ ImportVar(tag="useEffect"), ImportVar(tag="useState"), - ImportVar(tag="useEffect", alias="uE"), + ImportVar(tag="useEffect", alias="effectAlias"), ] From b968b302ea9cbc6922566198902291f9a1459145 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 20:04:27 +0000 Subject: [PATCH 4/4] Type the chained-ops regression test via Var.to(int) for pyright Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g --- tests/units/vars/test_base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/units/vars/test_base.py b/tests/units/vars/test_base.py index 4ed3a77535d..a814009d681 100644 --- a/tests/units/vars/test_base.py +++ b/tests/units/vars/test_base.py @@ -53,9 +53,8 @@ def test_chained_var_operations_var_data_stays_bounded() -> None: """ base = Var( _js_expr="a", - _var_type=int, _var_data=VarData(imports={"my-lib": [ImportVar(tag="thing")]}), - ).guess_type() + ).to(int) chained = base for i in range(18):