Skip to content

Performance: collapse 19 builtin transforms into a single Call dispatcher - #3167

Open
Pierre-Sassoulas wants to merge 2 commits into
mainfrom
perf/call-dispatcher
Open

Performance: collapse 19 builtin transforms into a single Call dispatcher#3167
Pierre-Sassoulas wants to merge 2 commits into
mainfrom
perf/call-dispatcher

Conversation

@Pierre-Sassoulas

Copy link
Copy Markdown
Member

Type of Changes

Type
🔨 Refactoring

Description

Split out of #3048, as requested — one optimization, one commit, tests included.

register_builtin_transform registered 19 separate transforms on nodes.Call. The transform visitor ran all 19 predicates per Call node, each repeating the same isinstance/name checks before all but one bailed out. This routes by name through a dict instead, so a Call node pays for those checks once.

The registration API is unchanged: register_builtin_transform now fills the dispatch table, so adding a builtin still takes one call and new entries enroll automatically.

tests/test_transforms.py gains coverage for the dispatcher: routing by name, unknown names falling through untouched, and the inference tips still firing for each registered builtin.

Refs #1115

``register_builtin_transform`` registered 19 separate transforms on
``nodes.Call``. The transform visitor ran all 19 predicates per Call
node, each repeating the same ``isinstance``/name checks before all but
one bailed out. Route by name through a dict instead, so a Call node
pays for the checks once.

The registration API is unchanged: ``register_builtin_transform`` now
fills the dispatch table, so adding a builtin still takes one call and
new entries enroll automatically.

Refs #1115
@codspeed-hq

codspeed-hq Bot commented Jul 30, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 5.64%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 3 improved benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation test_bench_endtoend_parse_flask 5.3 s 4.9 s +6.43%
Simulation test_bench_endtoend_walk_infer_flask 23.1 s 21.9 s +5.57%
Simulation test_bench_endtoend_walk_infer_black 35.7 s 34 s +4.95%

Tip

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


Comparing perf/call-dispatcher (e4bb930) with main (da4a8cf)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.66%. Comparing base (da4a8cf) to head (e4bb930).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3167      +/-   ##
==========================================
- Coverage   93.67%   93.66%   -0.01%     
==========================================
  Files          93       93              
  Lines       11645    11657      +12     
==========================================
+ Hits        10908    10919      +11     
- Misses        737      738       +1     
Flag Coverage Δ
linux 93.54% <100.00%> (+<0.01%) ⬆️
pypy 93.66% <100.00%> (-0.01%) ⬇️
windows 93.64% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
astroid/brain/brain_builtin_inference.py 94.01% <100.00%> (+0.13%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@DanielNoord DanielNoord left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Pretty hard to wrap you head around this haha

Tried my best, haven't looked at the tests yet

Comment on lines +1138 to 1148
# Builtins inference — registered through a single dispatcher to avoid
# running 19 separate predicates on every Call node walked by the
# transform visitor (#1115).
manager.register_transform(
nodes.Call,
inference_tip(_builtin_dispatch_transform),
_builtin_dispatch_predicate,
)
register_builtin_transform(manager, infer_bool, "bool")
register_builtin_transform(manager, infer_super, "super")
register_builtin_transform(manager, infer_callable, "callable")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are we still registering the transforms here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Didn't want to modify the API for register_builtin_transform (no leading underscore). If we remove this constraint, there's a lot we can do in a simpler way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't expect non core astroid to ever need to call register_builtin_transform, so I'd be fine with changing the API :)

inference_tip(_transform_wrapper),
partial(_builtin_filter_predicate, builtin_name=builtin_name),
)
del manager # No longer needed; dispatcher is registered once globally.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This feels dangerous. Is the del really necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, we could use a noqa instead. But the other discussion might make this one obsolete.

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