Repository navigation
Slow scan: uv/pylock hosted 4.1x/3.0x median ms/pkg (per-patch lock re-serialize) #836
Description
[agent] Bench: uv and pylock (PEP 751 pylock.toml) have both been flagged slow in 4 consecutive benchmark runs (2026-10-02 through 10-05). Their hosted scans cost about 3–4x the median ms per package across all package managers. They share one code path (PythonLockSession), so this issue covers both. Filing was deferred from the 10-04 run by the 3-new-issues-per-run cap.
| run | uv/hosted |
uv ms/pkg (x median) | pylock/hosted |
pylock ms/pkg (x median) |
|---|---|---|---|---|
| 2026-10-02 | 136.0 ms | 0.340 (flagged) | 113.6 ms | 0.284 (flagged) |
| 2026-10-03 | 143.9 ms | 0.360 (2.8x) | 142.1 ms | 0.355 (2.8x) |
| 2026-10-04 | 127.1 ms | 0.318 (2.8x) | 126.2 ms | 0.316 (2.8x) |
| 2026-10-05 | 244.0 ms | 0.610 (4.1x) | 179.8 ms | 0.449 (3.0x) |
Fixtures: 400 packages with 12 patched, so ms/pkg is high even at modest wall times. 10-05 ran on a slower runner, where the absolute times are about 1.4x those of earlier runs. The ratio to the cross-PM median is the signal.
Hot spot (callgrind, uv/hosted and pylock/hosted, 2026-10-05, main 045d7ec7)
utils::python_lock::PythonLockSession::rewrite: 39% (uv) / 38% (pylock) of all instructions. Most of it istoml_editDisplay for DocumentMut(31% / 35% inclusive, underencode::visit_table).- Cause: the lock is parsed once per session, but
PythonLockSession::rewriteends withdocument.to_string(). The caller (patch/redirect/mod.rs~L4675, thefor &(dep, sha256) in &usableloop) calls it once per patched dep and then diffs the full old and new text inrecord_python_lock_edits. So serialization is O(patches × lock size). - Possible fix: apply every planned edit to the one
DocumentMut, serialize once after the loop, and compute per-dep edit records from the document (or one final diff) instead of from full-text renderings. - Secondary:
vex::discover::pypi_locks::extractis 23% (uv) / 31% (pylock). Wiring discovery parses the same lock again withtoml_edit(toml_or_diag), separately from the redirect session. Sharing the parsed document would save another full parse.
Same shape as #760 (poetry) and #762 (pdm), which re-parse per patch; here it's the re-serialization.
Runner
4 vCPU, Intel(R) Xeon(R) Processor @ 2.80GHz (cloud sandbox), main 045d7ec7. The weekly A/B vs 2463257a (#277) is flat (uv/hosted −0.5%, pylock/hosted +3.8%), so this is standing cost, not a regression.
Repro
CARGO_PROFILE_PERF_INHERITS=release CARGO_PROFILE_PERF_LTO=thin CARGO_PROFILE_PERF_STRIP=none \
cargo build --locked --profile perf -p socket-patch-cli -p socket-patch-bench
target/perf/socket-patch-bench run --bin target/perf/socket-patch -f '^uv/' -f '^pylock/' -v
# profile: serve the fixture, then prefix the printed command's binary with
# valgrind --tool=callgrind
target/perf/socket-patch-bench serve uv/hosted --bin target/perf/socket-patchTracked in the ledger of #575 (standing slow-systems list).
Generated by Claude Code
Backlog review — 2026-10-08
Priority: P1 → P2. The benchmark shows extra serialization cost at hundreds of milliseconds, without a user latency budget or outage. Worth optimizing, not P1.
Activity
mikolalysenko commentedon Oct 5, 2026
[agent] Triaged as
priority:p1(uv / PEP 751 pylock). I confirmed onmain(045d7ec):rewrite_uv_lockinpatch/redirect/mod.rs(~L4637–4708) callsPythonLockSession::rewrite, which renders the whole document for each dep, then runs a full-textrecord_python_lock_editsdiff. That makes the cost O(patches × lock size). This has the same shape as #760 / #762 but is not the same root cause: those live inrewrite_poetry_lock_in/rewrite_pdm_lock_in(utils/poetry_lock.rs,utils/pdm_lock.rs) and re-parse rather than re-serialize. So I'm cross-linking them, not clustering them. I found no duplicate and no open PR.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 triage: P3, not a release blocker. The reported uv/pylock scans are roughly 0.1-0.2 seconds for hundreds of packages. P3 optimization after the release interface and correctness work.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.