]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-138535: Optimize fill_time for typical timestamps (#138537)
authorJeffrey Bosboom <jbosboom@jeffreybosboom.com>
Tue, 9 Sep 2025 09:05:54 +0000 (02:05 -0700)
committerGitHub <noreply@github.com>
Tue, 9 Sep 2025 09:05:54 +0000 (11:05 +0200)
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>
Lib/test/test_os.py
Misc/NEWS.d/next/Library/2025-09-06-20-09-32.gh-issue-138535.mlntEe.rst [new file with mode: 0644]
Modules/posixmodule.c

index b476b431ad6f9639209ceb316da863b223a1b5f6..cd15aa10f16de8fe8e06ee1953cf0f4102125691 100644 (file)
@@ -1064,9 +1064,15 @@ class UtimeTests(unittest.TestCase):
         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
diff --git a/Misc/NEWS.d/next/Library/2025-09-06-20-09-32.gh-issue-138535.mlntEe.rst b/Misc/NEWS.d/next/Library/2025-09-06-20-09-32.gh-issue-138535.mlntEe.rst
new file mode 100644 (file)
index 0000000..3fa8f48
--- /dev/null
@@ -0,0 +1,2 @@
+Speed up :func:`os.stat` for files with reasonable timestamps. Contributed
+by Jeffrey Bosboom.
index 53b21e99376485d31ca684c6cc08704474a8d0ff..74edd28998b5a13ad9c73d8be0f288d6ea8635cb 100644 (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;
+            }
 
-    assert(!PyErr_Occurred());
-    res = 0;
+            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);
+        }
+    }
 
-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