GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GH-116380: Speed up `glob.glob()` by removing some system calls by barneygale · Pull Request #116392 · python/cpython

Conversation

@barneygale

@barneygale barneygale commented Mar 5, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Speed up glob.glob() and glob.iglob() by reducing the number of system calls made.

This unifies the implementations of globbing in the glob and pathlib modules.

Depends on

  • GH-117586: Speed up pathlib.Path.glob() by working with strings #117589
  • GH-115060: Speed up pathlib.Path.glob() by not scanning literal parts #117732

Filtered recursive walk

Expanding a recursive ** segment entails walking the entire directory tree, and so any subsequent pattern segments (except special segments) can be evaluated by filtering the expanded paths through a regex. For example, glob.glob("foo/**/*.py", recursive=True) recursively walks foo/ with os.scandir(), and then filters paths through a regex based on "**/*.py, with no further filesystem access needed.

This solves #104269 as a side-effect.

Tracking path existence

We store a flag alongside each path indicating whether the path is guaranteed to exist. As we process the pattern:

  • Certain special pattern segments ("", "." and "..") leave the flag unchanged
  • Literal pattern segments (e.g. foo/bar) set the flag to false
  • Wildcard pattern segments (e.g. */*.py) set the flag to true (because children are found via os.scandir())
  • Recursive pattern segments (e.g. **) leave the flag unchanged for the root path, and set it to true for descendants discovered via os.scandir().

If the flag is false at the end, we call lstat() on each path to filter out missing paths.

Minor speed-ups

We:

  • Exclude paths that don't match a non-terminal non-recursive wildcard pattern prior to calling is_dir().
  • Use a stack rather than recursion to implement recursive wildcards.
    • Addresses Change shutil.rmtree and os.walk to support very deep hierarchies #89727 for the glob module.
  • Pre-compile regular expressions and pre-join literal pattern segments.
  • Convert to/from bytes (a minor use-case) in iglob() rather than supporting bytes throughout. This particularly simplifies the code needed to handle relative bytes paths with dir_fd.
  • Avoid calling os.path.join(); instead we keep paths in a normalized form and append trailing slashes when needed.
  • Avoid calling os.path.normcase(); instead we use case-insensitive regex matching.

Implementation notes

Much of this functionality is already present in pathlib's implementation of globbing. The specific additions we make are:

  1. Support for dir_fd
  2. Support for include_hidden
  3. Support for generating paths relative to root_dir

Results

Speedups via python -m timeit -s "from glob import glob" "glob(pattern, recursive=True, include_hidden=True)" from CPython source directory on Linux:

pattern speedup
Lib/* 1.87x
Lib/*/ 1.85x
Lib/*.py 1.3x
Lib/** 5.62x
Lib/**/ 1.23x
Lib/**/* 1.92x
Lib/**/** 17x
Lib/**/*/ 2.15x
Lib/**/*.py 1.79x
Lib/**/__init__.py 1.03x
Lib/**/*/*.py 2.41x
Lib/**/*/__init__.py 1.76x
  • Issue: Speed up glob.glob() by reducing number of system calls made #116380

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

momincks and dg-pb reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
@barneygale
pythonGH-116380: Make glob.glob() twice as fast
db3c620
@barneygale barneygale added the performance Performance or resource usage label Mar 5, 2024
@bedevere-app bedevere-app Bot added the awaiting core review label Mar 5, 2024
@bedevere-app bedevere-app Bot mentioned this pull request Mar 5, 2024
Speed up glob.glob() by reducing number of system calls made #116380
Open
@barneygale
Use os.listdir() if we don't need to check entry type.
9e1f059
@barneygale

barneygale commented Mar 5, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Needs a fix for #116377 to land.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 8 commits March 6, 2024 01:21
@barneygale
A few small speedups.
10432df
@barneygale
Simplify prefix removal
7e389e2
@barneygale
Re-implement glob0(), glob1(), and has_magic().
8680a0a
@barneygale
Fix errant StopIteration.
3bf3124
@barneygale
Skip compiling pattern for consecutive ** segments.
f8fb992
@barneygale
Clarify regex/path building in literal and recursive selectors.
50ef080
@barneygale
Simplify code to ignore root_dir.
ccefacd
@barneygale
Fix possible Windows separator issue.
fa951f6
gpshead
gpshead reviewed Mar 6, 2024
View reviewed changes

@gpshead gpshead left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Nice work!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Misc/NEWS.d/next/Library/2024-03-05-23-08-11.gh-issue-116380.56HU7I.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Privat33r-dev
Privat33r-dev reviewed Mar 6, 2024
View reviewed changes
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Privat33r-dev
Privat33r-dev reviewed Mar 6, 2024
View reviewed changes
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka
serhiy-storchaka self-requested a review March 6, 2024 17:12
barneygale added 5 commits March 6, 2024 21:20
@barneygale
Address some review feedback.
0aec12c
@barneygale
Use assignment expressions in a couple of places
72691ba
@barneygale
Replace lambda with operator.not_.
c58dd21
@barneygale
Merge branch 'main' into pythongh-116380
c361ec9
@barneygale
Speed up _add_trailing_slash()
22b30db
barneygale
barneygale commented Mar 7, 2024
View reviewed changes
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 2 commits March 7, 2024 02:10
@barneygale
Speed up select_literal()
83b70bd
@barneygale
Speed up select_recursive()
1d32d14
serhiy-storchaka
serhiy-storchaka reviewed Mar 7, 2024
View reviewed changes

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Please do not hurry to merge. This is an old code. The main advantage of the initial code was its simplicity, but since then it was complicated by adding new features and optimizations. In particularly the use of os.scandir() instead of os.listdir() significantly improved performance. The new implementation should be benchmarked with different test cases: deep and wide threes, files and directories domination.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale and Privat33r-dev reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Mar 7, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Thanks Serhiy! We use os.scandir() if either:

  1. We're expanding a recursive wildcard (we need to distinguish directories in order to recurse)
  2. We're expanding a non-final non-recursive wildcard (we need to select only directories)

If neither of these are true, then we don't need to stat() the children, and so os.listdir() is actually a little faster I think. But I will test this on a few machines to be sure!

edit: to further illustrate what I mean, here's where os.listdir() is used:

non-recursive part recursive part
non-terminal part os.scandir() os.scandir()
terminal part os.listdir() <-- os.scandir()
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Mar 10, 2024

Copy link
Copy Markdown
Contributor Author

The new implementation should be benchmarked with different test cases: deep and wide threes, files and directories domination.

I've been looking into this! The randomfiletree project is helpful - it can repeatedly walk a tree and create child files/folders according to a gaussian distribution, which seems to me like a good approximation for an average "shallow and wide" filesystem structure, including tweaking for file or folder distribution.

It's difficult to produce "deep and narrow" trees this way, as the file/folder probability would need to change with the depth (I think?). I've been considering writing a tree generator that works this way, e.g.:

  • At depth==0, generate 100 subdirectories
  • At 0 < depth < 50, generate 1 subdirectory
  • At depth==50, generate 100 files

... but is that overly arbitrary? Is there a better way? Or do I just need to come up with a bunch of test cases along those lines?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Mar 15, 2024

Copy link
Copy Markdown
Contributor Author

A test of 100 nested directories named "deep" from my Linux machine:

pattern speedup
deep/** 3.86x
deep/**/ 4.03x
deep/**/* 4.92x
deep/**/*/ 4.93x
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 4 commits February 4, 2025 15:59
@barneygale
Merge branch 'main' into pythongh-116380
e113457
@barneygale
Merge branch 'main' into pythongh-116380
d8ec2d4
@barneygale
Merge branch 'main' into pythongh-116380
203b78b
@barneygale
Windows test fixes
ffeb81a
@barneygale

barneygale commented Feb 8, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

@serhiy-storchaka FYI, this PR changes some of the test expectations you recently added in 8b5c850. Specifically, redundant slashes in the pattern are always preserved, and pattern separators are always normalized. Hopefully that's a reasonable price to pay for the speedups, the support for deep hierarchies, and the elimination of duplicate results

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 5 commits February 8, 2025 08:22
@barneygale
Speed up suppression of initial empty path.
ec157a1
@barneygale
Anticipate the removal of glob0() and glob1()
7a620db
@barneygale
Merge branch 'main' into pythongh-116380
e9fa3ac
@barneygale
Handle _initial_path_exists more explicitly in select_special()
46429ad
@barneygale
Add comment, remove redundant check
f160e8c
eendebakpt
eendebakpt reviewed Feb 8, 2025
View reviewed changes
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 8 commits February 9, 2025 22:44
@barneygale
Avoid slicing prefix when root_dir is None
47846ca
@barneygale
Replace _initial_path_exists sentinel with empty boolean arg
e55d946
@barneygale
Slightly simplify recursive globbing
47499c7
@barneygale
Revert "Slightly simplify recursive globbing"
849de87
This reverts commit 47499c7.
@barneygale
Split out method for scanning cwd
10a6cf4
@barneygale
Merge branch 'main' into pythongh-116380
413b12e
@barneygale
Merge branch 'main' into pythongh-116380
acf0f9b
@barneygale
Merge branch 'main' into pythongh-116380
a89211f
@barneygale

barneygale commented Feb 21, 2025

Copy link
Copy Markdown
Contributor Author

I'll merge this in about a week if there's no further feedback. Thanks all.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale
Merge branch 'main' into pythongh-116380
e4c519c
@barneygale
barneygale enabled auto-merge (squash) February 28, 2025 19:42
@barneygale
barneygale merged commit da4899b into python:main Feb 28, 2025
@bedevere-app bedevere-app Bot removed the awaiting core review label Feb 28, 2025
@eendebakpt

eendebakpt commented Feb 28, 2025

Copy link
Copy Markdown
Contributor

Great!

@barneygale Thanks for all the work you put into this PR.

barneygale and encukou reacted with heart emoji
All reactions
  • ❤️ 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot

bedevere-bot commented Feb 28, 2025

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot aarch64 Fedora Stable Clang Installed 3.x has failed when building commit da4899b.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/14/builds/7291) and take a look at the build logs.
  4. Check if the failure is related to this commit (da4899b) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/14/builds/7291

Failed tests:

  • test_glob

Failed subtests:

  • test_iglob_iter_close - test.test_glob.GlobTests.test_iglob_iter_close

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/home/buildbot/buildarea/3.x.cstratak-fedora-stable-aarch64.clang-installed/build/target/lib/python3.14/test/test_glob.py", line 424, in test_iglob_iter_close
    self.assertEqual(next(iter), 'deep/d')
                     ~~~~^^^^^^
StopIteration
barneygale reacted with eyes emoji
All reactions
  • 👀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot

bedevere-bot commented Feb 28, 2025

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot AMD64 Fedora Stable Clang Installed 3.x has failed when building commit da4899b.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/350/builds/7433) and take a look at the build logs.
  4. Check if the failure is related to this commit (da4899b) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/350/builds/7433

Failed tests:

  • test_glob

Failed subtests:

  • test_iglob_iter_close - test.test_glob.GlobTests.test_iglob_iter_close

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/home/buildbot/buildarea/3.x.cstratak-fedora-stable-x86_64.clang-installed/build/target/lib/python3.14/test/test_glob.py", line 424, in test_iglob_iter_close
    self.assertEqual(next(iter), 'deep/d')
                     ~~~~^^^^^^
StopIteration
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale added a commit to barneygale/cpython that referenced this pull request Mar 1, 2025
@barneygale
Revert "pythonGH-116380: Speed up glob.[i]glob() by making fewer sy…
43a9885
…stem calls. (python#116392)"

This broke tests on the 'aarch64 Fedora Stable Clang Installed 3.x' and
'AMD64 Fedora Stable Clang Installed 3.x' build bots.

This reverts commit da4899b.
barneygale added a commit that referenced this pull request Mar 1, 2025
@barneygale
Revert "GH-116380: Speed up glob.[i]glob() by making fewer system c…
5326c27
…alls. (#116392)" (#130743)

This broke tests on the 'aarch64 Fedora Stable Clang Installed 3.x' and
'AMD64 Fedora Stable Clang Installed 3.x' build bots.

This reverts commit da4899b.
@barneygale barneygale mentioned this pull request Aug 6, 2025
GH-116380: Speed up glob.[i]glob() by making fewer system calls (take 2) #137474
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@eendebakpt eendebakpt eendebakpt left review comments
@eryksun eryksun eryksun left review comments
@picnixz picnixz picnixz approved these changes
@serhiy-storchaka serhiy-storchaka Awaiting requested review from serhiy-storchaka
@gpshead gpshead Awaiting requested review from gpshead
+1 more reviewer
@Privat33r-dev Privat33r-dev Privat33r-dev left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

performance Performance or resource usage

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

8 participants

@barneygale @eendebakpt @picnixz @bedevere-bot @gpshead @eryksun @serhiy-storchaka @Privat33r-dev
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.