Skip to content

Load each config file and probe each candidate only once in UpwardSearch - #4567

Open
alexander-beedie wants to merge 1 commit into
facebook:mainfrom
alexander-beedie:perf-config-load-dedup
Open

Load each config file and probe each candidate only once in UpwardSearch#4567
alexander-beedie wants to merge 1 commit into
facebook:mainfrom
alexander-beedie:perf-config-load-dedup

Conversation

@alexander-beedie

@alexander-beedie alexander-beedie commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Overview

UpwardSearch was doing unnecessary work; in most packages it wasn't a problem, but in some it could trigger a lot of redundant filesystem work by repeating a lot of load/stat calls on the same file(s).

Solution

  • Optimise-out redundant IO/syscalls from UpwardSearch (config load/probes) by using simple SmallMap caching, keyed by path.

  • Before this PR, one config search would load the same file and stat the same candidate paths multiple times; now it only does each once.

Benchmarks1

There's a nice positive impact (on a limited number of packages), but it can be even more significant in corporate environments running endpoint security software.

Note: I was finally able to deterministically reproduce the increased cost of syscalls/IO caused by corporate endpoint security software introspection, so I've included it in the timings below (refs: #3993, #4017 (comment)).

  • The "taxed" columns show the additional overhead from endpoint security software. The real standout is pywin32, with a ~5-6x overhead: ~0.74s normally, ~4.29 secs with the security software tax (reduced to ~2.83 secs with this PR).

    Package Speedup (s) Δ Taxed speedup (s) Taxed Δ Tax
    vision 0.54 → 0.36 -32.50% 0.54 → 0.37 -31.70% 1.0×
    materialize 1.95 → 1.48 -23.30% 1.97 → 1.48 -24.80% 1.0×
    kornia 0.55 → 0.45 -19.10% 0.58 → 0.46 -20.10% 1.0×
    pycryptodome 0.28 → 0.24 -15.70% 0.29 → 0.24 -15.90% 1.0×
    beartype 0.45 → 0.38 -15.60% 0.47 → 0.39 -14.70% 1.0×
    mitmproxy 0.57 → 0.48 -14.90% 0.60 → 0.52 -13.60% 1.1×
    graphql-core 0.38 → 0.33 -13.80% 0.38 → 0.33 -12.20% 1.0×
    poetry 0.91 → 0.81 -10.90% 1.34 → 1.06 -21.10% 1.5×
    spack 0.55 → 0.50 -9.30% 0.58 → 0.53 -8.60% 1.1×
    pip 0.66 → 0.60 -8.90% 0.63 → 0.59 -5.60% 1.0×
    core 4.14 → 4.02 -3.40% 5.26 → 4.86 -7.30% 1.3×
    psycopg 0.65 → 0.63 -2.80% 1.88 → 1.36 -27.60% 2.9×
    pylox 0.56 → 0.55 -2.50% 1.77 → 1.27 -28.10% 3.2×
    archinstall 0.76 → 0.75 -2.40% 1.46 → 1.43 -3.40% 1.9×
    manticore 0.67 → 0.66 -2.20% 1.73 → 1.21 -27.50% 2.6×
    pywin32 0.74 → 0.72 -2.00% 4.29 → 2.83 -33.30% 5.8×
    dd-trace-py 1.63 → 1.57 -2.00% 2.25 → 1.99 -4.80% 1.4×
    narwhals 1.05 → 1.04 -2.00% 2.67 → 2.57 -4.30% 2.5×
    scipy 1.08 → 1.04 -2.00% 1.95 → 1.81 -3.40% 1.8×
  • Speedups are almost entirely wall-clock, not CPU(load/stat costs are IO, not compute).

  • Projects with no .venv benefit most, as they have to discover their own interpreter, triggering redundant config loads. The "taxed" winners are projects with a large site-packages tree which can trigger more syscalls.

Test Plan

  • All existing unit tests pass, one new test added.

  • Was able to trigger the endpoint security cost via symlink indirection to the test corpus (eg: on macOS /tmp is a symlink to /private/tmp, so /tmp/primer_repos/… crosses one symlink and /private/tmp/primer_repos/… crosses none; identical files/inodes/bytes, but it's the difference between a syscall costing ~0.7 µs or ~1.28 ms (~1000x more expensive, ouch 🚫)

Footnotes

  1. Test machine: Apple Silicon M3 Max (16 cores).

@meta-codesync

meta-codesync Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D116264606. (Because this pull request was imported automatically, there will not be any future comments.)

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