GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-148085: `datetime` cache `time` module lookups by maurycy · Pull Request #148088 · python/cpython Conversation
|
| Benchmark | main-c50d6cd | datetime-cache-time-module-lookups-ae32bfd |
|---|---|---|
| datetime.timetuple | 260 ns | 124 ns: 2.10x faster |
| datetime.strftime | 784 ns | 517 ns: 1.52x faster |
| date.timetuple | 258 ns | 124 ns: 2.08x faster |
| Geometric mean | (ref) | 1.88x faster |
For:
import pyperf, datetime
r = pyperf.Runner()
dt = datetime.datetime(2026, 4, 4, 12, 30, 45)
d = datetime.date(2026, 4, 4)
r.bench_func("datetime.timetuple", dt.timetuple)
r.bench_func("datetime.strftime", dt.strftime, "%Y-%m-%d")
r.bench_func("date.timetuple", d.timetuple)
For:
2026-04-04T14:17:02.794335000+0200 maurycy@gimel /Users/maurycy/work/cpython-main (main c50d6cd) % ./python.exe -c "import sysconfig; print(sysconfig.get_config_var('CONFIG_ARGS'))"
'--enable-optimizations' '--with-lto'
[127] 2026-04-04T14:17:24.011667000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (datetime-cache-time-module-lookups a49f862) % ./python.exe -c "import sysconfig; print(sysconfig.get_config_var('CONFIG_ARGS'))"
'--enable-optimizations' '--with-lto'
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We don't have a precedent for lazy caching, I think it's always eager? I'm worried this might cause issues, historically datetime and caching hasn't worked out.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@StanFromIreland We do: https://github.com/python/cpython/blob/dea4083aa95/Modules/arraymodule.c#L2561 https://github.com/python/cpython/blob/dea4083aa95/Modules/_decimal/_decimal.c#L3699 I copied my approach from there. I'm OK with moving to I believe the reason why it didn't work is that it relied on a global variable, instead of per-interpreter module. I left a note in #148085 in the previous discussions feature section. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I remember there were issues with datetime and the capsule API so be careful there. Likewise, if you do per-state caching, it may break existing hooks (removing the module from sys.modules will not make it reload I think) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
How real of a concern is this? I've grepped the code and I think we already tend to break monkey-patching: Line 371 in fbdbea9
Line 2562 in fbdbea9
Line 2765 in fbdbea9
cpython/Modules/_asynciomodule.c Line 4257 in fbdbea9
cpython/Modules/_decimal/_decimal.c Line 3700 in fbdbea9
I definitely do not want to cause any regression, but this seems to be a pattern, and the performance gain (on repeated runs) is real. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I would say it could break in tests. It's not uncommon to patch time-related modules when working with datetime I'd say. I honestly don't know if this is a real issue but I just wanted you to be aware of this possibility. In the past, I added NEWS entries indicating that imports were changed for performance reason just for users to know that the reference is no more local/global. In this case, it'd be easy to fix if someone first mock |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Hi @maurycy , great work on this. Since this module declares I noticed |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@picnixz Thank you, improved the NEWS entry: ff27dc4. @htjworld Thank you. I think you're 100% right and all lazy import caches should use CAS. I believe this is the correct pattern: Lines 1173 to 1188 in 7e0a0be
@picnixz Do you think this is indeed the correct pattern, and https://github.com/python/cpython/blob/dea4083aa95/Modules/arraymodule.c#L2561-L2567 and https://github.com/python/cpython/blob/dea4083aa95/Modules/_decimal/_decimal.c#L3699 are prone? Probably even: Line 609 in a0c57a8
cpython/Objects/genericaliasobject.c Line 580 in a0c57a8
cpython/Objects/genericaliasobject.c Line 844 in a0c57a8
I no longer think lazy is not worth the complexity. I've reverted it in 33c64f2. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@StanFromIreland A friendly ping :-) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Hi @maurycy, sorry for the delay, however I'm reluctant to merge this. It would (probably) break Also CC'ing @bblommers |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Thank you! Great point. No rush. I've prepared a simple regression test: import freezegun import time_machine import datetime import os import time os.environ["TZ"] = "UTC" time.tzset() FROZEN = datetime.datetime(2010, 6, 15, 12, 0, 0) DATE = datetime.date(2010, 6, 15) with freezegun.freeze_time(FROZEN): assert datetime.date.today() == DATE assert datetime.datetime.now() == FROZEN assert datetime.datetime.today() == FROZEN assert DATE.strftime("%Y-%m-%d %A") == "2010-06-15 Tuesday" assert DATE.timetuple().tm_year == 2010 print("freezegun: ok") with time_machine.travel(FROZEN, tick=False): assert datetime.datetime.now() == FROZEN assert DATE.strftime("%Y-%m-%d %A") == "2010-06-15 Tuesday" assert datetime.date.today() == DATE assert datetime.datetime.today() == FROZEN print("time-machine: ok") And I've got news for you 😂 2026-05-14T11:42:52.278765000+0200 maurycy@gimel /Users/maurycy/work/cpython (datetime-cache-time-module-lookups e00aa62?) % uv run --with freezegun --with time-machine --python 3.11 /tmp/czasumierzenie.py freezegun: ok time-machine: ok 2026-05-14T11:42:52.820308000+0200 maurycy@gimel /Users/maurycy/work/cpython (datetime-cache-time-module-lookups e00aa62?) % uv run --with freezegun --with time-machine --python 3.12 /tmp/czasumierzenie.py freezegun: ok time-machine: ok 2026-05-14T11:43:01.349321000+0200 maurycy@gimel /Users/maurycy/work/cpython (datetime-cache-time-module-lookups e00aa62?) % uv run --with freezegun --with time-machine --python 3.13 /tmp/czasumierzenie.py freezegun: ok time-machine: ok 2026-05-14T11:43:03.558151000+0200 maurycy@gimel /Users/maurycy/work/cpython (datetime-cache-time-module-lookups e00aa62?) % uv run --with freezegun --with time-machine --python 3.14 /tmp/czasumierzenie.py freezegun: ok time-machine: ok 2026-05-14T11:43:05.601936000+0200 maurycy@gimel /Users/maurycy/work/cpython (datetime-cache-time-module-lookups e00aa62?) % uv run --with freezegun --with time-machine --python 3.15 /tmp/czasumierzenie.py freezegun: ok Traceback (most recent call last): File "/tmp/czasumierzenie.py", line 24, in <module> assert datetime.date.today() == DATE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError Using 2026-05-14T11:55:58.141343000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (git)-[v3.15.0a6~249|bisect]?- % cat /tmp/bisekt.sh #!/usr/bin/env bash set -u cd "$(git rev-parse --show-toplevel)" make -j"$(nproc)" >/tmp/bisect-build.log 2>&1 PY=./python.exe [ -x "$PY" ] || exit 125 uv run --with freezegun --with time-machine --python "$PY" /tmp/czasumierzenie.py 2026-05-14T11:56:43.035141000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (main b6854c5?) % git bisect start main fc3e22a06c9~1 -- Modules/_datetimemodule.c Bisecting: 3 revisions left to test after this (roughly 2 steps) [f5685a266b252455e03ef8e6055eaf4007ec749d] gh-80620: Support negative timestamps on windows in `time.gmtime`, `time.localtime`, and `datetime` module (#143463) 2026-05-14T11:57:32.147048000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (git)-[v3.15.0a6~249|bisect]?- % git bisect run /tmp/bisekt.sh running '/tmp/bisekt.sh' freezegun: ok Traceback (most recent call last): File "/tmp/czasumierzenie.py", line 24, in <module> assert datetime.date.today() == DATE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError Bisecting: 0 revisions left to test after this (roughly 1 step) [49fb46f555881c9f2d20ca87c8187c8718217c77] gh-139774: use relaxed atomics for datetime hashes (#139775) running '/tmp/bisekt.sh' freezegun: ok Traceback (most recent call last): File "/tmp/czasumierzenie.py", line 24, in <module> assert datetime.date.today() == DATE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError Bisecting: 0 revisions left to test after this (roughly 0 steps) [fc3e22a06c9b7c93ee379b5554a40a275745e15f] gh-88473: Implement fast path in date.today() for date types (#130980) running '/tmp/bisekt.sh' freezegun: ok Traceback (most recent call last): File "/tmp/czasumierzenie.py", line 24, in <module> assert datetime.date.today() == DATE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError fc3e22a06c9b7c93ee379b5554a40a275745e15f is the first bad commit commit fc3e22a06c9b7c93ee379b5554a40a275745e15f Author: Stan Ulbrych <[email protected]> Date: 2025-09-16 10:28:54 +0100 gh-88473: Implement fast path in date.today() for date types (#130980) Co-authored-by: Bénédikt Tran <[email protected]> .../2025-03-08-17-07-00.gh-issue-88473.qg23g8.rst | 3 ++ Modules/_datetimemodule.c | 34 +++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-03-08-17-07-00.gh-issue-88473.qg23g8.rst bisect found first bad commit 2026-05-14T12:01:43.310131000+0200 maurycy@gimel /Users/maurycy/src/github.com/maurycy/cpython (git)-[bisect/bad|bisect]?- % And it's... fc3e22a (cc @picnixz) cc @adamchainz ref #130980 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
FWIW I don't think we can be beholden to how these mocking libraries work. These kinds of libraries is that they are often digging around in the internal implementation details to get themselves to work, so the nature of the beast is that they need to keep up if we break stuff (though if it's basically free for us to not break something then obviously we shouldn't break it just to break it). If doing something that is a real improvement to the code as it is used makes it impossible to write a library of this type, my intuition is that instead of avoiding breaking them forever, we should shift gears to injecting hooks into the library to give a blessed "we won't break you" abstraction to mocking libraries, but I'm guessing it won't come to that. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Yes, however, they are quite prominent nowadays, so I think we should give them some consideration. Although I think they could simply switch to using pydatetime to avoid this, so it's not the end of the world. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
💯 feel free to break time-machine, as long as it's possible to still mock something. I'll fix it up! But also: this PR will not break it, afaict. time-machine monkey-patches C-level function pointers, which aren't moving around here :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Some last little nits.
Technically it is possible for time.strftime to be missing (if HAVE_STRFTIME is false), and now datetime would fail to import. However, we don’t currently support (and I’m not aware of) any such platforms.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Documentation build overview
186 files changed ·
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This PR is stale because it has been open for 90 days with no activity. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Sorry it took so long.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
…97yTo.rst Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Stan Ulbrych <[email protected]>
There was an error while loading. Please reload this page.
|
🤖 New build scheduled with the buildbot fleet by @StanFromIreland for commit e206500 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F148088%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Co-authored-by: Stan Ulbrych <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.