gh-150818: Speed up logging.getLogger() for existing loggers (GH-150825)#150825
Conversation
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.
defd276 to
9c0df96
Compare
Both branches inside the lock assign rv before it is read, so the reset is dead code.
|
🤖 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. |
|
Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13. |
|
Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14. |
|
Thanks @gaborbernat for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15. |
|
GH-150927 is a backport of this pull request to the 3.13 branch. |
|
GH-150928 is a backport of this pull request to the 3.14 branch. |
|
GH-150929 is a backport of this pull request to the 3.15 branch. |
|
We don't usually backport features? |
|
Is performance improvements a feature? 🤔 |
|
@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? |
|
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. |
|
As documented in the devguide:
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. |
|
I've raised a discussion topic as that same para in the devguide says
|
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
loggerDictwith a singledict.get(), which is atomic under both the GIL and free threading, and aLoggeris 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 (pyperf)
Run base vs patched by swapping
Lib/logging/__init__.pyon the same interpreter. The names are realgetLogger()string arguments mined from the top-1000 corpus.Resolves #150818.