Skip to content

Commit d4825ac

Browse files
jbosboomvstinner
andauthored
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 <[email protected]>
1 parent 5edfe55 commit d4825ac

File tree

3 files changed

+62
-41
lines changed

3 files changed

+62
-41
lines changed

Lib/test/test_os.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,9 +1064,15 @@ def test_large_time(self):
10641064
if self.get_file_system(self.dirname) != "NTFS":
10651065
self.skipTest("requires NTFS")
10661066

1067-
large = 5000000000 # some day in 2128
1068-
os.utime(self.fname, (large, large))
1069-
self.assertEqual(os.stat(self.fname).st_mtime, large)
1067+
times = (
1068+
5000000000, # some day in 2128
1069+
# boundaries of the fast path cutoff in posixmodule.c:fill_time
1070+
-9223372037, -9223372036, 9223372035, 9223372036,
1071+
)
1072+
for large in times:
1073+
with self.subTest(large=large):
1074+
os.utime(self.fname, (large, large))
1075+
self.assertEqual(os.stat(self.fname).st_mtime, large)
10701076

10711077
def test_utime_invalid_arguments(self):
10721078
# seconds and nanoseconds parameters are mutually exclusive
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :func:`os.stat` for files with reasonable timestamps. Contributed
2+
by Jeffrey Bosboom.

Modules/posixmodule.c

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,55 +2588,68 @@ static int
25882588
fill_time(PyObject *module, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec)
25892589
{
25902590
assert(!PyErr_Occurred());
2591-
2592-
int res = -1;
2593-
PyObject *s_in_ns = NULL;
2594-
PyObject *ns_total = NULL;
2595-
PyObject *float_s = NULL;
2596-
2597-
PyObject *s = _PyLong_FromTime_t(sec);
2598-
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
2599-
if (!(s && ns_fractional)) {
2600-
goto exit;
2601-
}
2602-
2603-
s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion);
2604-
if (!s_in_ns) {
2605-
goto exit;
2606-
}
2607-
2608-
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
2609-
if (!ns_total)
2610-
goto exit;
2611-
2612-
float_s = PyFloat_FromDouble(sec + 1e-9*nsec);
2613-
if (!float_s) {
2614-
goto exit;
2615-
}
2591+
#define SEC_TO_NS (1000000000LL)
2592+
assert(nsec < SEC_TO_NS);
26162593

26172594
if (s_index >= 0) {
2595+
PyObject *s = _PyLong_FromTime_t(sec);
2596+
if (s == NULL) {
2597+
return -1;
2598+
}
26182599
PyStructSequence_SET_ITEM(v, s_index, s);
2619-
s = NULL;
26202600
}
2601+
26212602
if (f_index >= 0) {
2603+
PyObject *float_s = PyFloat_FromDouble((double)sec + 1e-9 * nsec);
2604+
if (float_s == NULL) {
2605+
return -1;
2606+
}
26222607
PyStructSequence_SET_ITEM(v, f_index, float_s);
2623-
float_s = NULL;
26242608
}
2609+
2610+
int res = -1;
26252611
if (ns_index >= 0) {
2626-
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
2627-
ns_total = NULL;
2628-
}
2612+
/* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */
2613+
if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) {
2614+
PyObject *ns_total = PyLong_FromLongLong(sec * SEC_TO_NS + nsec);
2615+
if (ns_total == NULL) {
2616+
return -1;
2617+
}
2618+
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
2619+
assert(!PyErr_Occurred());
2620+
res = 0;
2621+
}
2622+
else {
2623+
PyObject *s_in_ns = NULL;
2624+
PyObject *ns_total = NULL;
2625+
PyObject *s = _PyLong_FromTime_t(sec);
2626+
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
2627+
if (s == NULL || ns_fractional == NULL) {
2628+
goto exit;
2629+
}
26292630

2630-
assert(!PyErr_Occurred());
2631-
res = 0;
2631+
s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion);
2632+
if (s_in_ns == NULL) {
2633+
goto exit;
2634+
}
2635+
2636+
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
2637+
if (ns_total == NULL) {
2638+
goto exit;
2639+
}
2640+
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
2641+
assert(!PyErr_Occurred());
2642+
res = 0;
2643+
2644+
exit:
2645+
Py_XDECREF(s);
2646+
Py_XDECREF(ns_fractional);
2647+
Py_XDECREF(s_in_ns);
2648+
}
2649+
}
26322650

2633-
exit:
2634-
Py_XDECREF(s);
2635-
Py_XDECREF(ns_fractional);
2636-
Py_XDECREF(s_in_ns);
2637-
Py_XDECREF(ns_total);
2638-
Py_XDECREF(float_s);
26392651
return res;
2652+
#undef SEC_TO_NS
26402653
}
26412654

26422655
#ifdef MS_WINDOWS

0 commit comments

Comments
 (0)