gh-138535: Optimize fill_time for typical timestamps (#138537)

While file timestamps can be anything the file system can store, most
lie between the recent past and the near future.  Optimize fill_time()
for typical timestamps in three ways:

- When possible, convert to nanoseconds with C arithmetic.
- When using C arithmetic and the seconds member is not required (for
  st_birthtime), avoid creating a long object.
- When using C arithmetic, reorder the code to avoid the null checks
  implied in Py_XDECREF().

Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Jeffrey Bosboom 2025-09-09 02:05:54 -07:00 committed by GitHub
parent 5edfe55acf
commit d4825ac27c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 41 deletions

View file

@ -1064,9 +1064,15 @@ def test_large_time(self):
if self.get_file_system(self.dirname) != "NTFS":
self.skipTest("requires NTFS")
large = 5000000000 # some day in 2128
os.utime(self.fname, (large, large))
self.assertEqual(os.stat(self.fname).st_mtime, large)
times = (
5000000000, # some day in 2128
# boundaries of the fast path cutoff in posixmodule.c:fill_time
-9223372037, -9223372036, 9223372035, 9223372036,
)
for large in times:
with self.subTest(large=large):
os.utime(self.fname, (large, large))
self.assertEqual(os.stat(self.fname).st_mtime, large)
def test_utime_invalid_arguments(self):
# seconds and nanoseconds parameters are mutually exclusive

View file

@ -0,0 +1,2 @@
Speed up :func:`os.stat` for files with reasonable timestamps. Contributed
by Jeffrey Bosboom.

View file

@ -2588,55 +2588,68 @@ static int
fill_time(PyObject *module, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec)
{
assert(!PyErr_Occurred());
int res = -1;
PyObject *s_in_ns = NULL;
PyObject *ns_total = NULL;
PyObject *float_s = NULL;
PyObject *s = _PyLong_FromTime_t(sec);
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
if (!(s && ns_fractional)) {
goto exit;
}
s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion);
if (!s_in_ns) {
goto exit;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
if (!ns_total)
goto exit;
float_s = PyFloat_FromDouble(sec + 1e-9*nsec);
if (!float_s) {
goto exit;
}
#define SEC_TO_NS (1000000000LL)
assert(nsec < SEC_TO_NS);
if (s_index >= 0) {
PyObject *s = _PyLong_FromTime_t(sec);
if (s == NULL) {
return -1;
}
PyStructSequence_SET_ITEM(v, s_index, s);
s = NULL;
}
if (f_index >= 0) {
PyObject *float_s = PyFloat_FromDouble((double)sec + 1e-9 * nsec);
if (float_s == NULL) {
return -1;
}
PyStructSequence_SET_ITEM(v, f_index, float_s);
float_s = NULL;
}
int res = -1;
if (ns_index >= 0) {
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
ns_total = NULL;
/* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */
if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) {
PyObject *ns_total = PyLong_FromLongLong(sec * SEC_TO_NS + nsec);
if (ns_total == NULL) {
return -1;
}
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
assert(!PyErr_Occurred());
res = 0;
}
else {
PyObject *s_in_ns = NULL;
PyObject *ns_total = NULL;
PyObject *s = _PyLong_FromTime_t(sec);
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
if (s == NULL || ns_fractional == NULL) {
goto exit;
}
s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion);
if (s_in_ns == NULL) {
goto exit;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
if (ns_total == NULL) {
goto exit;
}
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
assert(!PyErr_Occurred());
res = 0;
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
}
}
assert(!PyErr_Occurred());
res = 0;
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
Py_XDECREF(ns_total);
Py_XDECREF(float_s);
return res;
#undef SEC_TO_NS
}
#ifdef MS_WINDOWS