Skip to content

commands: fast path for --sh, replace sort|uniq with sort -u#3423

Merged
native-api merged 2 commits intopyenv:masterfrom
jakelodwick:inline-sh-commands
Mar 13, 2026
Merged

commands: fast path for --sh, replace sort|uniq with sort -u#3423
native-api merged 2 commits intopyenv:masterfrom
jakelodwick:inline-sh-commands

Conversation

@jakelodwick
Copy link
Copy Markdown
Contributor

@jakelodwick jakelodwick commented Mar 12, 2026

pyenv-commands --sh iterates all pyenv-* files in PATH, then filters for the sh- prefix. It pipes through sort and uniq as separate processes.

Three changes, all inside pyenv-commands:

  1. --sh fast path: globs pyenv-sh-* directly instead of globbing all pyenv-* and filtering. Skips iterating non-sh commands entirely.
  2. sort -u replaces sort | uniq (one fewer process, all modes).
  3. IFS=:; for path in $PATH iterates PATH entries directly instead of allocating an array.

Benchmark (hyperfine --shell=none, 100 runs, Apple M2):

--sh mode:
  upstream    6.3 ms ± 0.5 ms
  patched     5.7 ms ± 0.3 ms    1.10× faster

default mode (sort -u + IFS only):
  upstream    6.2 ms ± 0.3 ms
  patched     5.9 ms ± 0.4 ms    1.05× faster

The timing gain is small. The code is cleaner: --sh does less work, dedup is one process instead of two, PATH iteration has fewer intermediaries.

All 234 tests pass, including bash 3.2 (make test-unit-docker-3.2.57).

@jakelodwick jakelodwick requested review from a team as code owners March 12, 2026 11:16
Copy link
Copy Markdown
Member

@native-api native-api left a comment

Choose a reason for hiding this comment

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

The declared time gain is quite small, can be within the statistical discrepancy (with your scripts, I got some unstable benchmark results; maybe using smth like hyperfine will produce more stable figures). Is this the biggest remaining hot spot? If not, this could be premature optimization.

The same effect if not more can likely be achieved by optimizing pyenv-commands. The same algorithm that we ultimately used in pyenv-versions seems applicable here: https://unix.stackexchange.com/questions/798374/using-find-to-find-a-file-in-path

Comment thread libexec/pyenv-init Outdated
Comment thread libexec/pyenv-init Outdated
@jakelodwick
Copy link
Copy Markdown
Contributor Author

Revised per your feedback. The optimization now lives in pyenv-commands itself:

  • --sh fast path: globs pyenv-sh-* directly instead of iterating all pyenv-* and filtering
  • sort -u replaces sort | uniq (all modes)
  • IFS=:; for path in $PATH as you suggested

pyenv-init reverted to calling pyenv-commands --sh.

I tried the find-based approach from the StackExchange link too. Slower at typical PATH sizes (7-9ms vs 4ms) due to find startup overhead, so the narrowed glob was the better fit.

Thanks for the hyperfine suggestion, hadn't used it before. Results with --shell=none, 100 runs:

--sh mode:
  upstream    6.3 ms ± 0.5 ms    [5.8 … 8.3 ms]
  working     5.7 ms ± 0.3 ms    [5.2 … 6.9 ms]
  1.10× faster

default mode (sort -u + IFS only):
  upstream    6.2 ms ± 0.3 ms    [5.8 … 7.4 ms]
  working     5.9 ms ± 0.4 ms    [5.5 … 9.3 ms]
  1.05× faster

The timing improvement is marginal. But the code is cleaner either way: --sh does less work, sort -u is one process instead of two, PATH iteration is simpler. Happy to close this if the gain doesn't justify the diff.

When called with --sh, glob only pyenv-sh-* files instead of
all pyenv-* files. This skips iterating non-sh commands entirely.

For all modes, replace the sort | uniq pipeline with sort -u
(one fewer process) and iterate PATH directly with
IFS=:; for path in $PATH instead of allocating an array.
@jakelodwick jakelodwick changed the title init: inline sh-command discovery commands: fast path for --sh, replace sort|uniq with sort -u Mar 12, 2026
Copy link
Copy Markdown
Member

@native-api native-api left a comment

Choose a reason for hiding this comment

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

I wouldn't call the code "cleaner" at all. Now we have lots of duplication: two very similar loops, two sort -u calls; overriding IFS for the rest of the script.

I've tried to radically change the algorithm from loops to a pipeline -- but that didn't save any more time than your version, either.

So I've eliminated the duplicate sort call and IFS override while keeping the separate loop.

If you're trying to optimize init, probably look into what other hot spots are remaining so as to make a real difference; maybe replacing eval "$(pyenv init)" with source <something> (like e.g. OhMyZsh does) will be faster. 'Cuz this PR's approach looks rather unproductive: we've just spent 2 days for no real gain. Also could check other performance-related issues that I've linked from the previous PR. E.g. the problem with Pyenv-Virtualenv's prompt command is real bad.

@native-api native-api merged commit 6a246fa into pyenv:master Mar 13, 2026
20 checks passed
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.

3 participants