Use PyObject_CallMethodOneArg in datetime call_tzinfo_method #151059
Copy link
Copy link
Closed
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirperformancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement
Description
Feature or enhancement
Proposal:
The call_tzinfo_method() (invoked from tzinfo.utcoffset() and tzinfo.dst()) use:
offset = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);On every call this parses the "O" format string, builds a temporary argument tuple, and rebuilds the method-name str (PyObject_GetAttrString). Switching to PyObject_CallMethodOneArg with interned strings improves performance.
Benchmarks
Wall-clock, PGO+LTO build
| operation | before | after | speedup |
|---|---|---|---|
astimezone() UTC→local |
225.3 ns | 124.3 ns | 1.81× |
timestamp() |
461.5 ns | 270.2 ns | 1.71× |
isoformat() (aware) |
667.2 ns | 542.1 ns | 1.23× |
utcoffset() (direct) |
116.6 ns | 48.4 ns | 2.41× |
now().isoformat() (control) |
664.8 ns | 681.8 ns | 0.98× |
tzname() (control) |
48.8 ns | 48.3 ns | 1.01× |
Benchmark script
"""Microbenchmark for datetime call_tzinfo_method (gh-150942)."""
import datetime as dt
import time
from datetime import timezone
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
utc_dt = dt.datetime(2024, 6, 7, 16, 30, tzinfo=timezone.utc)
ny_dt = dt.datetime(2024, 6, 7, 12, 30, tzinfo=ny)
def bench(fn, N=1_000_000, repeat=7):
best = float("inf")
for _ in range(repeat):
start = time.perf_counter()
for _ in range(N):
fn()
best = min(best, (time.perf_counter() - start) / N * 1e9)
return best
CASES = {
"astimezone() UTC->local": lambda: utc_dt.astimezone(ny),
"timestamp()": lambda: ny_dt.timestamp(),
"isoformat()": lambda: ny_dt.isoformat(),
"utcoffset()": lambda: ny_dt.utcoffset(),
"now().isoformat()": lambda: dt.datetime.now().isoformat(), # naive: control
"tzname() [control]": lambda: ny_dt.tzname(),
}
print(f"{'operation':<26}{'ns/op':>10}")
print("-" * 36)
for name, fn in CASES.items():
print(f"{name:<26}{bench(fn):>9.1f}")Has this already been discussed elsewhere?
No response given
Links to previous discussion of this feature:
No response
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirperformancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement
Projects
- StatusShow more project fieldsDone
Activity
pythongh-151059: Use PyObject_CallMethodOneArg in datetime call_tzinf…
gh-151059: [perf] Use `PyObject_CallMethodOneArg` in datetime's `call…
pythongh-151059: [perf] Use `PyObject_CallMethodOneArg` in datetime's…