FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-150818: Speed up logging.getLogger() for existing loggers (GH-150825) by gaborbernat · Pull Request #150825 · python/cpython · GitHub

Repository navigation

gh-150818: Speed up logging.getLogger() for existing loggers (GH-150825) - #150825

Merged
vsajip merged 2 commits into
python:mainfrom
gaborbernat:opt/logging-getlogger-fastpath
Jun 4, 2026
Merged

vsajip merged 2 commits into
python:mainfrom
gaborbernat:opt/logging-getlogger-fastpath

Conversation

gaborbernat commented Jun 2, 2026 •
edited by bedevere-app Bot
Loading

Copy link
Copy Markdown
Contributor

logging.getLogger(name) returns the singleton logger for a name, creating it on first use. Every call takes the logging lock, even the overwhelmingly common case where the logger already exists and is returned unchanged. Libraries fetch their logger by name throughout their code, at module import and again inside functions, so the same handful of names are looked up over and over for the life of a process. Those repeat lookups are pure overhead: the logger is already there.

This returns an existing, fully-initialised logger through a lock-free fast path before falling back to the locked section. The fast path reads loggerDict with a single dict.get(), which is atomic under both the GIL and free threading, and a Logger is inserted into that dict only after it is fully constructed under the lock, so the fast path never observes a half-built object or a placeholder. First-time creation, placeholder resolution and the parent/child wiring still run under the lock exactly as before.

Resolving logger names collected from the top-1000 corpus improves from 6.68 µs to 5.02 µs, 33% faster.

Benchmark base patched
getLogger, existing loggers 6.68 µs 5.02 µs: 33% faster
Benchmark (pyperf)

Run base vs patched by swapping Lib/logging/__init__.py on the same interpreter. The names are real getLogger() string arguments mined from the top-1000 corpus.

import logging, pyperf

names = ["elastic_transport.transport", "peewee.pool", "LiteLLM", "httpx",
    "uvicorn.error", "uvicorn.access", "fsspec", "boto3", "posthog", "amqp",
    "nox", "tldextract.cache", "dulwich.lfs", "urllib3.connectionpool",
    "asyncio", "sqlalchemy.engine", "werkzeug", "django.request", "celery.worker",
    "kafka.conn", "redis.client", "paramiko.transport", "matplotlib.font_manager"]
for n in names:
    logging.getLogger(n)   # pre-create, as modules do at import

runner = pyperf.Runner()
runner.bench_func("getLogger %d existing loggers" % len(names),
                  lambda: [logging.getLogger(n) for n in names])

Resolves #150818.

getLogger() took the logging lock on every call, including the common case of
an already-registered logger. Return that logger through a lock-free fast path
backed by an atomic dict lookup. First-time creation, placeholder resolution
and parent/child wiring still run under the lock, and the fast path is safe
under both the GIL and free threading.
gaborbernat force-pushed the opt/logging-getlogger-fastpath branch from defd276 to 9c0df96 Compare June 2, 2026 23:45
Comment thread Lib/logging/__init__.py Outdated
Both branches inside the lock assign rv before it is read, so the
reset is dead code.
gaborbernat requested a review from sobolevn June 3, 2026 14:42
vsajip added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jun 3, 2026

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @vsajip for commit efad324 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F150825%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jun 3, 2026
vsajip changed the title gh-150818: Speed up logging.getLogger() for existing loggers gh-150818: Speed up logging.getLogger() for existing loggers (GH-150825) Jun 4, 2026
vsajip merged commit e833f57 into python:main Jun 4, 2026
138 of 152 checks passed
vsajip added needs backport to 3.13 only security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes labels Jun 4, 2026

Copy link
Copy Markdown

Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13.
🐍🍒⛏🤖 I'm not a witch! I'm not a witch!

Copy link
Copy Markdown

Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14.
🐍🍒⛏🤖

Copy link
Copy Markdown

Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖

bedevere-app Bot commented Jun 4, 2026

Copy link
Copy Markdown

GH-150927 is a backport of this pull request to the 3.13 branch.

bedevere-app Bot removed the needs backport to 3.13 only security fixes label Jun 4, 2026

bedevere-app Bot commented Jun 4, 2026

Copy link
Copy Markdown

GH-150928 is a backport of this pull request to the 3.14 branch.

bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Jun 4, 2026

bedevere-app Bot commented Jun 4, 2026

Copy link
Copy Markdown

GH-150929 is a backport of this pull request to the 3.15 branch.

bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Jun 4, 2026

Copy link
Copy Markdown
Member

We don't usually backport features?

Copy link
Copy Markdown
Contributor Author

Is performance improvements a feature? 🤔

vsajip commented Jun 4, 2026

Copy link
Copy Markdown
Member

@StanFromIreland As there is no API change, I've assumed it would be OK to back-port so that earlier versions get the benefit too. Is there some precedent for not doing that?

vsajip commented Jun 4, 2026

Copy link
Copy Markdown
Member

I see that the issue was marked as a feature, by which I suppose it meant that it wasn't a bug. But it's not a feature in the sense of an API enhancement or fix.

Copy link
Copy Markdown
Member

As documented in the devguide:

The only changes allowed to occur in a maintenance branch without debate are bug fixes, test improvements, and edits to the documentation.

And also:

The only branch that receives new features is main

Neither of those places elaborate on the reasoning for this, but I think it is largely to avoid introducing any possible regressions, i.e., to preserve backwards compatibility.

vsajip commented Jun 4, 2026

Copy link
Copy Markdown
Member

I've raised a discussion topic as that same para in the devguide says

only rare exceptions are accepted and must be discussed first.

Comment thread Lib/logging/__init__.py
# returned without taking the lock. dict.get() is atomic under both
# the GIL and free threading, and a Logger is fully initialised before
# being inserted into loggerDict under the lock, so this never sees a
# partially-constructed object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

self._fixupParents(rv) is called after self.loggerDict[name] = rv.
Therefore, it is possible to get a logger whose logger.parent has not yet been set.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

Speed up logging.getLogger() for already-registered loggers

6 participants


Back | FazBrowse Home | New Git URL