Skip to content

perf(python): Skip the Pragma Lookup in Files Without One - #23702

Merged
benjyw merged 2 commits into
pantsbuild:mainfrom
jbylund:perf/python-pragma-lookup
Oct 15, 2026
Merged

benjyw merged 2 commits into
pantsbuild:mainfrom
jbylund:perf/python-pragma-lookup

Conversation

@jbylund

@jbylund jbylund commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

What

For every string literal that could be a string import candidate, the Rust import parser (ImportCollector::handle_string_candidate) looks for a trailing # pants: no-infer-dep comment by climbing the syntax tree with Node::parent() and Node::next_named_sibling(). In tree-sitter both are linear in the number of siblings, so a long module pays a lot for each string. The same lookup runs after every import statement and __import__ call (is_pragma_ignored).

A file that does not contain the text # pants: no-infer-dep cannot have such a comment. This checks that once per file, when the collector is created, and returns early from both lookups when the text is absent. The result is unchanged.

Disclosure: investigated and implemented with claude.

@jbylund

jbylund commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Evidence

On a large private monorepo (about 65.8k targets, 33.9k Python files), a cold pants peek :: with --rule-threads-core=2, 5 alternating pairs against main at the same commit:

main This PR Difference
Wall 89.36s 86.65s -2.71s (-3.0%)
CPU (user+sys) 163.5s 155.7s -7.8s (-4.8%)

The output was identical to main's in every pair. The per-pair numbers and the method follow.

Tests

The existing parser tests (62 in dep_inference, including the pragma tests for imports, strings and nested nodes) pass.

Supporting details

Profile

A macOS sample profile of a cold peek :: on the same repo (--rule-threads-core=2, about 8.6s window): 16.5% of all thread-samples were tree-sitter import parsing. Of the tree-sitter samples (20.8k), Node::next_named_sibling accounted for 3.7k and Node::parent for 3.3k, reached from get_dependencies. The recursive lookup runs once per accepted string literal (handle_string_candidate), the plain one once per import statement, import ... from statement and __import__ call.

A/B

Control is main at 5ec37d437; the candidate is this branch on top of it. Both use the same engine build settings and the same dev venv. Cold runs: empty store and named caches, no daemon, no local or remote cache; the named caches (Coursier, PEX) start from the same copy on both sides. Pants runs from source on CPython 3.14 (the GIL build) on an 18-core Apple Silicon machine, with --rule-threads-core=2. Run order alternates between pairs.

Pair First main This PR Difference CPU (user+sys)
1 control 90.08s 88.47s -1.61s 164.1s -> 158.6s
2 candidate 87.64s 87.86s +0.22s 161.3s -> 157.3s
3 control 88.97s 84.62s -4.35s 162.1s -> 151.8s
4 candidate 90.60s 85.13s -5.47s 165.7s -> 153.1s
5 control 89.52s 87.19s -2.33s 164.1s -> 157.6s
Mean 89.36s 86.65s -2.71s (-3.0%) 163.5s -> 155.7s (-7.8s, -4.8%)
  • Output: each candidate run's target data was checked against the control run from the same pair: same targets in the same order, same keys, identical values for dependencies, sources and the other fields a consumer reads.
  • Outside load: the machine wasn't otherwise idle. Processes outside the benchmark used 1.1 to 2.1 cores on average during the runs. Pair 5's candidate run averaged 2.1 cores; without that pair the mean is -2.8s wall and -8.1s CPU. Wall time varies more between pairs (+0.22s to -5.47s) than CPU does.
  • Elsewhere: the same change on a tree with other performance changes (where the GIL thread is no longer the bottleneck) measured -1.15s (-6.2%) wall and about -10% CPU, with the same ~8 CPU-seconds saved.

Every string literal that could be a string import candidate climbed the syntax tree with parent() and next_named_sibling() looking for a trailing `# pants: no-infer-dep` comment, which are linear in the number of siblings. About a third of the tree-sitter time in a cold peek of a large repo was spent there. A file that does not contain the pragma text cannot have one, so check once per file.
@jbylund
jbylund force-pushed the perf/python-pragma-lookup branch from a34975d to 81323fc Compare October 14, 2026 17:09

@benjyw benjyw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Neat

@benjyw
benjyw merged commit d91c51a into pantsbuild:main Oct 15, 2026
28 checks passed
@jbylund
jbylund deleted the perf/python-pragma-lookup branch October 16, 2026 01:47
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.

2 participants