Skip to content

perf: optimize get_all_nested_components with stack iteration - #1039

Open
saquibsaifee wants to merge 1 commit into
CycloneDX:mainfrom
saquibsaifee:perf-optimize-nested-components-8794458850882209436
Open

saquibsaifee wants to merge 1 commit into
CycloneDX:mainfrom
saquibsaifee:perf-optimize-nested-components-8794458850882209436

Conversation

@saquibsaifee

Copy link
Copy Markdown
Contributor

Description

💡 What: Replaced the recursive function with an iterative stack-based approach.
🎯 Why: To avoid intermediate set allocations and recursive call overhead which was impacting performance for deep component trees.
📊 Measured Improvement: Improved execution time from ~123 seconds to ~55 seconds for 100 iterations of a 6-deep, 4-broad component tree (a >50% improvement).

AI Tool Disclosure

  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: Gemini Jules
    • LLMs and versions: Gemini 3.1 Pro

Affirmation

@saquibsaifee
saquibsaifee requested a review from a team as a code owner September 1, 2026 21:15
@saquibsaifee

Copy link
Copy Markdown
Contributor Author

The old recursive get_all_nested_components implementation suffered from performance and memory issues on deep/broad component trees because every step of the recursion would allocate a new set and call set.update() to merge sets from all child nodes upwards. These multiple allocations and merging operations cause high overhead.

By replacing the recursion with an iterative stack-based approach, we entirely avoid function call overhead and recursion limits (which protects against RecursionError in deeply nested graphs). We also avoid creating intermediate sets—everything is added directly to one single final set. Additionally, checking if current not in components: adds protection against cycles in the component graph (which would have caused infinite recursion in the old code).

As verified by tests, this retains the exact same functionality while delivering a significant (55% measured) performance boost and better safety guarantees.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@read-the-docs-community

read-the-docs-community Bot commented Sep 1, 2026

Copy link
Copy Markdown

Signed-off-by: saquibsaifee <saquibsaifee2@gmail.com>
@saquibsaifee
saquibsaifee force-pushed the perf-optimize-nested-components-8794458850882209436 branch from a5c0f4b to c758f7e Compare September 1, 2026 21:36
@jkowalleck

jkowalleck commented Sep 15, 2026

Copy link
Copy Markdown
Member

i see, the performance of the function is just bad, mostly due to the costly __hash__ implementation we cannot change right now (backwards compatibility, ...).

maybe this is even more performant:

def iter_all_nested_components(self, include_self: bool = False) -> Iterator['Component']:
    """
    Iterate over this component and all its nested components (assemblies), recursively.

    Components are deduplicated by object identity — NOT by equality.
    This never calls the (costly) ``Component.__hash__``/``__eq__``,
    which makes it dramatically faster than :func:`get_all_nested_components`
    for large component trees. Safe against cyclic component graphs.

    Order of iteration is not guaranteed.
    """
    seen: dict[int, 'Component'] = {}
    stack: list['Component'] = [self] if include_self else list(self.components)
    while stack:
        current = stack.pop()
        current_id = id(current)
        if current_id not in seen:
            seen[current_id] = current
            stack.extend(current.components)
    return iter(seen.values())

def get_all_nested_components(self, include_self: bool = False) -> set['Component']:
    return set(self.iter_all_nested_components(include_self=include_self))

and then, lets replace all aplicable internal calls on get_all_nested_components() with iter_all_nested_components()

What do you think, @saquibsaifee ?

@jkowalleck jkowalleck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants