Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is `datetime.date.today` slower than `datetime.datetime.now` in Python?

Tags:

python

Out of curiosity, I did the following benchmark of these two functions:

In [12]: %timeit datetime.datetime.now()
100000 loops, best of 3: 5.09 µs per loop

In [13]: %timeit datetime.date.today()
100000 loops, best of 3: 6.4 µs per loop

I thought date object involved less information, so it should be the faster one, but it turned out to be slower.

What could be the reason?

like image 637
satoru Avatar asked Nov 27 '22 07:11

satoru


1 Answers

I got nerd-sniped by this today so I'll show you what I found -- strap in.

first, the implementation of date.today() must go through a member function call -- this lookup appears to be the slow part:

https://github.com/python/cpython/blob/b2bf2bc1ece673d387341e06c8d3c2bc6e259747/Modules/_datetimemodule.c#L2886-L2892

excerpted here:

static PyObject *
date_today(PyObject *cls, PyObject *dummy)
{
    PyObject *time;
    PyObject *result;
    _Py_IDENTIFIER(fromtimestamp);

    time = time_time();
    if (time == NULL)
        return NULL;

    /* Note well:  today() is a class method, so this may not call
     * date.fromtimestamp.  For example, it may call
     * datetime.fromtimestamp.  That's why we need all the accuracy
     * time.time() delivers; if someone were gonzo about optimization,
     * date.today() could get away with plain C time().
     */
    result = _PyObject_CallMethodIdOneArg(cls, &PyId_fromtimestamp, time);
    Py_DECREF(time);
    return result;
}

notably it's always going through the slow path

so I figured, why not give it a fast path?

$ git diff -w
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 8ef2dad37a..7eaa5d1740 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -2875,6 +2875,17 @@ date_fromtimestamp(PyObject *cls, PyObject *obj)
 static PyObject *
 date_today(PyObject *cls, PyObject *dummy)
 {
+    /* fast path, don't call fromtimestamp */
+    if ((PyTypeObject *)cls == &PyDateTime_DateType) {
+        struct tm tm;
+        time_t t;
+        time(&t);
+        localtime_r(&t, &tm);
+        return new_date_ex(tm.tm_year + 1900,
+                           tm.tm_mon + 1,
+                           tm.tm_mday,
+                           (PyTypeObject*)cls);
+    } else {
         PyObject *time;
         PyObject *result;
         _Py_IDENTIFIER(fromtimestamp);
@@ -2893,6 +2904,7 @@ date_today(PyObject *cls, PyObject *dummy)
         Py_DECREF(time);
         return result;
     }
+}
 
 /*[clinic input]
 @classmethod

zoom zoom

$ ./python -m timeit -s 'from datetime import date' 'date.today()'
500000 loops, best of 5: 407 nsec per loop
$ ./python -m timeit -s 'from datetime import datetime' 'datetime.now().date()'
500000 loops, best of 5: 764 nsec per loop
like image 160
Anthony Sottile Avatar answered Dec 18 '22 11:12

Anthony Sottile