diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2012-04-29 00:41:27 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2012-04-29 00:41:27 (GMT) |
commit | ec89539ccccd6103665a7a5c7234cf09f27c1c72 (patch) | |
tree | 5899bfa59a08ae9db3e630c2419ee87b4df6b101 /Modules/timemodule.c | |
parent | ca6e40f12a3145856abfe69d5cd8b97df6ebca95 (diff) | |
download | cpython-ec89539ccccd6103665a7a5c7234cf09f27c1c72.zip cpython-ec89539ccccd6103665a7a5c7234cf09f27c1c72.tar.gz cpython-ec89539ccccd6103665a7a5c7234cf09f27c1c72.tar.bz2 |
Issue #14428, #14397: Implement the PEP 418
* Rename time.steady() to time.monotonic()
* On Windows, time.monotonic() uses GetTickCount/GetTickCount64() instead of
QueryPerformanceCounter()
* time.monotonic() uses CLOCK_HIGHRES if available
* Add time.get_clock_info(), time.perf_counter() and time.process_time()
functions
Diffstat (limited to 'Modules/timemodule.c')
-rw-r--r-- | Modules/timemodule.c | 549 |
1 files changed, 460 insertions, 89 deletions
diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 771db83..1e843b9 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -4,9 +4,17 @@ #include <ctype.h> +#ifdef HAVE_SYS_TIMES_H +#include <sys/times.h> +#endif + #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> -#endif /* HAVE_SYS_TYPES_H */ +#endif + +#if defined(HAVE_SYS_RESOURCE_H) +#include <sys/resource.h> +#endif #ifdef QUICKWIN #include <io.h> @@ -45,12 +53,16 @@ /* Forward declarations */ static int floatsleep(double); -static PyObject* floattime(void); +static PyObject* floattime(_Py_clock_info_t *info); + +#ifdef MS_WINDOWS +static OSVERSIONINFOEX winver; +#endif static PyObject * time_time(PyObject *self, PyObject *unused) { - return floattime(); + return floattime(NULL); } PyDoc_STRVAR(time_doc, @@ -70,7 +82,7 @@ Fractions of a second may be present if the system clock provides them."); #endif static PyObject * -pyclock(void) +floatclock(_Py_clock_info_t *info) { clock_t value; value = clock(); @@ -80,15 +92,22 @@ pyclock(void) "or its value cannot be represented"); return NULL; } + if (info) { + info->implementation = "clock()"; + info->resolution = 1.0 / (double)CLOCKS_PER_SEC; + info->is_monotonic = 1; + info->is_adjusted = 0; + } return PyFloat_FromDouble((double)value / CLOCKS_PER_SEC); } #endif /* HAVE_CLOCK */ #if defined(MS_WINDOWS) && !defined(__BORLANDC__) +#define WIN32_PERF_COUNTER /* Win32 has better clock replacement; we have our own version, due to Mark Hammond and Tim Peters */ -static PyObject * -win32_clock(int fallback) +static int +win_perf_counter(_Py_clock_info_t *info, PyObject **result) { static LONGLONG cpu_frequency = 0; static LONGLONG ctrStart; @@ -102,28 +121,41 @@ win32_clock(int fallback) if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) { /* Unlikely to happen - this works on all intel machines at least! Revert to clock() */ - if (fallback) - return pyclock(); - else - return PyErr_SetFromWindowsErr(0); + *result = NULL; + return -1; } cpu_frequency = freq.QuadPart; } QueryPerformanceCounter(&now); diff = (double)(now.QuadPart - ctrStart); - return PyFloat_FromDouble(diff / (double)cpu_frequency); + if (info) { + info->implementation = "QueryPerformanceCounter()"; + info->resolution = 1.0 / (double)cpu_frequency; + info->is_monotonic = 1; + info->is_adjusted = 0; + } + *result = PyFloat_FromDouble(diff / (double)cpu_frequency); + return 0; } #endif -#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) +#if defined(WIN32_PERF_COUNTER) || defined(HAVE_CLOCK) +#define PYCLOCK +static PyObject* +pyclock(_Py_clock_info_t *info) +{ +#ifdef WIN32_PERF_COUNTER + PyObject *res; + if (win_perf_counter(info, &res) == 0) + return res; +#endif + return floatclock(info); +} + static PyObject * time_clock(PyObject *self, PyObject *unused) { -#if defined(MS_WINDOWS) && !defined(__BORLANDC__) - return win32_clock(1); -#else - return pyclock(); -#endif + return pyclock(NULL); } PyDoc_STRVAR(clock_doc, @@ -150,7 +182,6 @@ time_clock_gettime(PyObject *self, PyObject *args) PyErr_SetFromErrno(PyExc_IOError); return NULL; } - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); } @@ -787,11 +818,74 @@ the local timezone used by methods such as localtime, but this behaviour\n\ should not be relied on."); #endif /* HAVE_WORKING_TZSET */ +#if defined(MS_WINDOWS) || defined(__APPLE__) \ + || (defined(HAVE_CLOCK_GETTIME) \ + && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC))) +#define PYMONOTONIC +#endif + +#ifdef PYMONOTONIC static PyObject* -steady_clock(int strict) +pymonotonic(_Py_clock_info_t *info) { -#if defined(MS_WINDOWS) && !defined(__BORLANDC__) - return win32_clock(!strict); +#if defined(MS_WINDOWS) + static ULONGLONG (*GetTickCount64) (void) = NULL; + static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); + static int has_getickcount64 = -1; + double result; + + if (has_getickcount64 == -1) { + /* GetTickCount64() was added to Windows Vista */ + if (winver.dwMajorVersion >= 6) { + HINSTANCE hKernel32; + hKernel32 = GetModuleHandleW(L"KERNEL32"); + *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32, + "GetTickCount64"); + has_getickcount64 = (Py_GetTickCount64 != NULL); + } + else + has_getickcount64 = 0; + } + + if (has_getickcount64) { + ULONGLONG ticks; + ticks = Py_GetTickCount64(); + result = (double)ticks * 1e-3; + } + else { + static DWORD last_ticks = 0; + static DWORD n_overflow = 0; + DWORD ticks; + + ticks = GetTickCount(); + if (ticks < last_ticks) + n_overflow++; + last_ticks = ticks; + + result = ldexp(n_overflow, 32); + result += ticks; + result *= 1e-3; + } + + if (info) { + DWORD timeAdjustment, timeIncrement; + BOOL isTimeAdjustmentDisabled, ok; + if (has_getickcount64) + info->implementation = "GetTickCount64()"; + else + info->implementation = "GetTickCount()"; + info->is_monotonic = 1; + ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &isTimeAdjustmentDisabled); + if (!ok) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + info->resolution = timeIncrement * 1e-7; + info->is_adjusted = 0; + } + return PyFloat_FromDouble(result); + #elif defined(__APPLE__) static mach_timebase_info_data_t timebase; uint64_t time; @@ -805,88 +899,338 @@ steady_clock(int strict) time = mach_absolute_time(); secs = (double)time * timebase.numer / timebase.denom * 1e-9; - + if (info) { + info->implementation = "mach_absolute_time()"; + info->resolution = (double)timebase.numer / timebase.denom * 1e-9; + info->is_monotonic = 1; + info->is_adjusted = 0; + } return PyFloat_FromDouble(secs); -#elif defined(HAVE_CLOCK_GETTIME) - static int steady_clk_index = 0; - static int monotonic_clk_index = 0; - int *clk_index; - clockid_t steady_clk_ids[] = { -#ifdef CLOCK_MONOTONIC_RAW - CLOCK_MONOTONIC_RAW, + +#elif defined(HAVE_CLOCK_GETTIME) && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC)) + struct timespec tp; +#ifdef CLOCK_HIGHRES + const clockid_t clk_id = CLOCK_HIGHRES; + const char *function = "clock_gettime(CLOCK_HIGHRES)"; +#else + const clockid_t clk_id = CLOCK_MONOTONIC; + const char *function = "clock_gettime(CLOCK_MONOTONIC)"; #endif - CLOCK_MONOTONIC, - CLOCK_REALTIME - }; - clockid_t monotonic_clk_ids[] = { -#ifdef CLOCK_MONOTONIC_RAW - CLOCK_MONOTONIC_RAW, + + if (clock_gettime(clk_id, &tp) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (info) { + struct timespec res; + info->is_monotonic = 1; + info->implementation = function; +#if (defined(linux) || defined(__linux) || defined(__linux__)) \ + && !defined(CLOCK_HIGHRES) + /* CLOCK_MONOTONIC is adjusted on Linux */ + info->is_adjusted = 1; +#else + info->is_adjusted = 0; #endif - CLOCK_MONOTONIC - }; - clockid_t *clk_ids; - int clk_ids_len; - int ret; - struct timespec tp; + if (clock_getres(clk_id, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } + return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); +#endif +} - if (strict) { - clk_index = &monotonic_clk_index; - clk_ids = monotonic_clk_ids; - clk_ids_len = Py_ARRAY_LENGTH(monotonic_clk_ids); +static PyObject * +time_monotonic(PyObject *self, PyObject *unused) +{ + return pymonotonic(NULL); +} + +PyDoc_STRVAR(monotonic_doc, +"monotonic() -> float\n\ +\n\ +Monotonic clock, cannot go backward."); +#endif /* PYMONOTONIC */ + +static PyObject* +perf_counter(_Py_clock_info_t *info) +{ +#if defined(WIN32_PERF_COUNTER) || defined(PYMONOTONIC) + PyObject *res; +#endif +#if defined(WIN32_PERF_COUNTER) + static int use_perf_counter = 1; +#endif +#ifdef PYMONOTONIC + static int use_monotonic = 1; +#endif + +#ifdef WIN32_PERF_COUNTER + if (use_perf_counter) { + if (win_perf_counter(info, &res) == 0) + return res; + use_perf_counter = 0; } - else { - clk_index = &steady_clk_index; - clk_ids = steady_clk_ids; - clk_ids_len = Py_ARRAY_LENGTH(steady_clk_ids); +#endif + +#ifdef PYMONOTONIC + if (use_monotonic) { + res = pymonotonic(info); + if (res != NULL) + return res; + use_monotonic = 0; + PyErr_Clear(); } +#endif + + return floattime(info); +} + +static PyObject * +time_perf_counter(PyObject *self, PyObject *unused) +{ + return perf_counter(NULL); +} - while (0 <= *clk_index) { - clockid_t clk_id = clk_ids[*clk_index]; - ret = clock_gettime(clk_id, &tp); - if (ret == 0) - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); +PyDoc_STRVAR(perf_counter_doc, +"perf_counter() -> float\n\ +\n\ +Performance counter for benchmarking."); - (*clk_index)++; - if (clk_ids_len <= *clk_index) - (*clk_index) = -1; +static PyObject* +py_process_time(_Py_clock_info_t *info) +{ +#if defined(MS_WINDOWS) + HANDLE process; + FILETIME creation_time, exit_time, kernel_time, user_time; + ULARGE_INTEGER large; + double total; + BOOL ok; + + process = GetCurrentProcess(); + ok = GetProcessTimes(process, &creation_time, &exit_time, &kernel_time, &user_time); + if (!ok) + return PyErr_SetFromWindowsErr(0); + + large.u.LowPart = kernel_time.dwLowDateTime; + large.u.HighPart = kernel_time.dwHighDateTime; + total = (double)large.QuadPart; + large.u.LowPart = user_time.dwLowDateTime; + large.u.HighPart = user_time.dwHighDateTime; + total += (double)large.QuadPart; + if (info) { + info->implementation = "GetProcessTimes()"; + info->resolution = 1e-7; + info->is_monotonic = 1; + info->is_adjusted = 0; } - if (strict) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyFloat_FromDouble(total * 1e-7); +#else + +#if defined(HAVE_SYS_RESOURCE_H) + struct rusage ru; +#endif +#ifdef HAVE_TIMES + struct tms t; + static long ticks_per_second = -1; +#endif + +#if defined(HAVE_CLOCK_GETTIME) \ + && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) + struct timespec tp; +#ifdef CLOCK_PROF + const clockid_t clk_id = CLOCK_PROF; + const char *function = "clock_gettime(CLOCK_PROF)"; +#else + const clockid_t clk_id = CLOCK_PROCESS_CPUTIME_ID; + const char *function = "clock_gettime(CLOCK_PROCESS_CPUTIME_ID)"; +#endif + + if (clock_gettime(clk_id, &tp) == 0) { + if (info) { + struct timespec res; + info->implementation = function; + info->is_monotonic = 1; + info->is_adjusted = 0; + if (clock_getres(clk_id, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } + return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); } - return floattime(); +#endif + +#if defined(HAVE_SYS_RESOURCE_H) + if (getrusage(RUSAGE_SELF, &ru) == 0) { + double total; + total = ru.ru_utime.tv_sec + ru.ru_utime.tv_usec * 1e-6; + total += ru.ru_stime.tv_sec + ru.ru_stime.tv_usec * 1e-6; + if (info) { + info->implementation = "getrusage(RUSAGE_SELF)"; + info->is_monotonic = 1; + info->is_adjusted = 0; + info->resolution = 1e-6; + } + return PyFloat_FromDouble(total); + } +#endif + +#ifdef HAVE_TIMES + if (times(&t) != (clock_t)-1) { + double total; + + if (ticks_per_second == -1) { +#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) + ticks_per_second = sysconf(_SC_CLK_TCK); + if (ticks_per_second < 1) + ticks_per_second = -1; +#elif defined(HZ) + ticks_per_second = HZ; #else - if (strict) { - PyErr_SetString(PyExc_NotImplementedError, - "no steady clock available on your platform"); - return NULL; + ticks_per_second = 60; /* magic fallback value; may be bogus */ +#endif + } + + if (ticks_per_second != -1) { + total = (double)t.tms_utime / ticks_per_second; + total += (double)t.tms_stime / ticks_per_second; + if (info) { + info->implementation = "times()"; + info->is_monotonic = 1; + info->is_adjusted = 0; + info->resolution = 1.0 / ticks_per_second; + } + return PyFloat_FromDouble(total); + } } - return floattime(); +#endif + + return floatclock(info); #endif } static PyObject * -time_steady(PyObject *self, PyObject *args, PyObject *kwargs) +time_process_time(PyObject *self, PyObject *unused) { - static char *kwlist[] = {"strict", NULL}; - int strict = 0; + return py_process_time(NULL); +} + +PyDoc_STRVAR(process_time_doc, +"process_time() -> float\n\ +\n\ +Process time for profiling: sum of the kernel and user-space CPU time."); + + +static PyTypeObject ClockInfoType; + +PyDoc_STRVAR(ClockInfo_docstring, + "Clock information"); + +static PyStructSequence_Field ClockInfo_fields[] = { + {"implementation", "name of the underlying C function " + "used to get the clock value"}, + {"is_monotonic", "True if the clock cannot go backward, False otherwise"}, + {"is_adjusted", "True if the clock can be adjusted " + "(e.g. by a NTP daemon), False otherwise"}, + {"resolution", "resolution of the clock in seconds"}, + {NULL, NULL} +}; + +static PyStructSequence_Desc ClockInfo_desc = { + "time.clock_info", + ClockInfo_docstring, + ClockInfo_fields, + 4, +}; + +static PyObject * +time_get_clock_info(PyObject *self, PyObject *args) +{ + char *name; + PyObject *obj; + _Py_clock_info_t info; + PyObject *result; + + if (!PyArg_ParseTuple(args, "s:get_clock_info", &name)) + return NULL; + +#ifdef Py_DEBUG + info.implementation = NULL; + info.is_monotonic = -1; + info.is_adjusted = -1; + info.resolution = -1.0; +#else + info.implementation = ""; + info.is_monotonic = 0; + info.is_adjusted = 0; + info.resolution = 1.0; +#endif - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "|i:steady", kwlist, - &strict)) + if (strcmp(name, "time") == 0) + obj = floattime(&info); +#ifdef PYCLOCK + else if (strcmp(name, "clock") == 0) + obj = pyclock(&info); +#endif +#ifdef PYMONOTONIC + else if (strcmp(name, "monotonic") == 0) + obj = pymonotonic(&info); +#endif + else if (strcmp(name, "perf_counter") == 0) + obj = perf_counter(&info); + else if (strcmp(name, "process_time") == 0) + obj = py_process_time(&info); + else { + PyErr_SetString(PyExc_ValueError, "unknown clock"); + return NULL; + } + if (obj == NULL) return NULL; + Py_DECREF(obj); - return steady_clock(strict); + result = PyStructSequence_New(&ClockInfoType); + if (result == NULL) + return NULL; + + assert(info.implementation != NULL); + obj = PyUnicode_FromString(info.implementation); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 0, obj); + + assert(info.is_monotonic != -1); + obj = PyBool_FromLong(info.is_monotonic); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 1, obj); + + assert(info.is_adjusted != -1); + obj = PyBool_FromLong(info.is_adjusted); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 2, obj); + + assert(info.resolution > 0.0); + assert(info.resolution <= 1.0); + obj = PyFloat_FromDouble(info.resolution); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 3, obj); + + return result; + +error: + Py_DECREF(result); + return NULL; } -PyDoc_STRVAR(steady_doc, -"steady(strict=False) -> float\n\ +PyDoc_STRVAR(get_clock_info_doc, +"get_clock_info(name: str) -> dict\n\ \n\ -Return the current time as a floating point number expressed in seconds.\n\ -This clock advances at a steady rate relative to real time and it may not\n\ -be adjusted. The reference point of the returned value is undefined so only\n\ -the difference of consecutive calls is valid."); - +Get information of the specified clock."); static void PyInit_timezone(PyObject *m) { @@ -977,10 +1321,8 @@ PyInit_timezone(PyObject *m) { #endif /* __CYGWIN__ */ #endif /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ -#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_CLOCK_GETRES) -#ifdef CLOCK_REALTIME +#if defined(HAVE_CLOCK_GETTIME) PyModule_AddIntMacro(m, CLOCK_REALTIME); -#endif #ifdef CLOCK_MONOTONIC PyModule_AddIntMacro(m, CLOCK_MONOTONIC); #endif @@ -1002,7 +1344,7 @@ PyInit_timezone(PyObject *m) { static PyMethodDef time_methods[] = { {"time", time_time, METH_NOARGS, time_doc}, -#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) +#ifdef PYCLOCK {"clock", time_clock, METH_NOARGS, clock_doc}, #endif #ifdef HAVE_CLOCK_GETTIME @@ -1018,8 +1360,6 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_MKTIME {"mktime", time_mktime, METH_O, mktime_doc}, #endif - {"steady", (PyCFunction)time_steady, METH_VARARGS|METH_KEYWORDS, - steady_doc}, #ifdef HAVE_STRFTIME {"strftime", time_strftime, METH_VARARGS, strftime_doc}, #endif @@ -1027,6 +1367,12 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_WORKING_TZSET {"tzset", time_tzset, METH_NOARGS, tzset_doc}, #endif +#ifdef PYMONOTONIC + {"monotonic", time_monotonic, METH_NOARGS, monotonic_doc}, +#endif + {"process_time", time_process_time, METH_NOARGS, process_time_doc}, + {"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc}, + {"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc}, {NULL, NULL} /* sentinel */ }; @@ -1104,6 +1450,20 @@ PyInit_time(void) if (!initialized) { PyStructSequence_InitType(&StructTimeType, &struct_time_type_desc); + + /* initialize ClockInfoType */ + PyStructSequence_InitType(&ClockInfoType, &ClockInfo_desc); + Py_INCREF(&ClockInfoType); + PyModule_AddObject(m, "clock_info", (PyObject*)&ClockInfoType); + +#ifdef MS_WINDOWS + winver.dwOSVersionInfoSize = sizeof(winver); + if (!GetVersionEx((OSVERSIONINFO*)&winver)) { + Py_DECREF(m); + PyErr_SetFromWindowsErr(0); + return NULL; + } +#endif } Py_INCREF(&StructTimeType); PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType); @@ -1112,7 +1472,7 @@ PyInit_time(void) } static PyObject* -floattime(void) +floattime(_Py_clock_info_t *info) { _PyTime_timeval t; #ifdef HAVE_CLOCK_GETTIME @@ -1123,10 +1483,21 @@ floattime(void) because it would require to link Python to the rt (real-time) library, at least on Linux */ ret = clock_gettime(CLOCK_REALTIME, &tp); - if (ret == 0) + if (ret == 0) { + if (info) { + struct timespec res; + info->implementation = "clock_gettime(CLOCK_REALTIME)"; + info->is_monotonic = 0; + info->is_adjusted = 1; + if (clock_getres(CLOCK_REALTIME, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + } #endif - _PyTime_gettimeofday(&t); + _PyTime_gettimeofday_info(&t, info); return PyFloat_FromDouble((double)t.tv_sec + t.tv_usec * 1e-6); } |