Skip to content

Work around globbing performance in Python 3.12 - #1696

Merged
SirVer merged 6 commits into
masterfrom
sirver/1694-slow-snippet-directory-discovery
Sep 6, 2026
Merged

SirVer merged 6 commits into
masterfrom
sirver/1694-slow-snippet-directory-discovery

Conversation

@SirVer

@SirVer SirVer commented Sep 5, 2026 •

Copy link
Copy Markdown
Owner

find_all_snippet_directories() expands the wildcards Vim allows in 'runtimepath' entries. Since the pathlib migration (c84bb5a) it did so with Path(pth.anchor).glob(str(pth.relative_to(pth.anchor))), i.e. it globbed the whole path starting at /.

Python 3.12's pathlib has no fast path for literal pattern components: every component goes through _WildcardSelector, which scandirs the parent directory and calls is_dir()on each entry. With the ~100 entries of a plugin-heavy runtimepath that lists/, /home, /home/user`, ... once per entry (595 directory listings and 5763 stats in the report), and on WSL with symlinked network folders the first snippet expansion froze Vim for seconds.

Neither 3.11 nor 3.13 have this issue. 3.11 pathlib resolved literal pattern segments through a single stat. Python 3.12 removed that path in python/cpython#102710. Python 3.13 brought a literal fast path back in the glob rewrite (python/cpython#117732).

We work around this by finding globbing in the snippet paths and only start globbing from the segment that has globbing characters. While strictly only needed for 3.12 it does not hurt on other Pythons.

Fixes #1694.

find_all_snippet_directories() expanded the wildcards Vim allows in
'runtimepath' entries with Path(anchor).glob(relative_path), i.e. it
globbed the whole path starting at "/". Python 3.12's pathlib has no
fast path for literal pattern components, so every entry listed "/",
"/home", "/home/user", ... once. With ~100 runtimepath entries on a
slow filesystem the first snippet expansion froze Vim for seconds, and
an ancestor directory without list permission made the snippet
directory vanish because Path.glob swallows the PermissionError.

Literal entries are now a single existence check. Entries with a
wildcard glob only the tail starting at the first wildcard component.

Fixes #1694
Answers the review question whether snipMate file discovery shares the
runtimepath glob problem. It does not walk from the filesystem root:
its glob base is `<rtp>/snippets` and a missing directory costs one
is_dir() per pattern. It never expanded wildcards in runtimepath
entries at all though, so `snippets/` directories behind an entry like
`~/.vim/bundle/*` were silently skipped while UltiSnips-format
directories behind the same entry have been found since #1416.

Move the helper to common.py as expand_runtimepath_entry() and use it
for both sources. snipMate now honours the wildcards and skips entries
without a snippets directory with a single existence check.
@SirVer SirVer changed the title Stop globbing every runtimepath entry from the filesystem root Work around globbing performance in Python 3.12 Sep 6, 2026
@SirVer
SirVer merged commit 81d7ceb into master Sep 6, 2026
15 checks passed
@SirVer
SirVer deleted the sirver/1694-slow-snippet-directory-discovery branch September 6, 2026 10:34
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.

find_all_snippet_directories() is very slow

1 participant