From 343574245f7f3fed48ff236acd4e641c16260a07 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 28 Oct 2015 14:39:49 +0530 Subject: [PATCH] Speedup monotonic() by eliminating the ctypes overhead --- setup/extensions.py | 4 ++ src/calibre/constants.py | 1 + src/calibre/srv/tests/loop.py | 13 ++++ src/calibre/utils/monotonic.c | 85 +++++++++++++++++++++++++ src/calibre/utils/monotonic.py | 113 ++------------------------------- 5 files changed, 109 insertions(+), 107 deletions(-) create mode 100644 src/calibre/utils/monotonic.c diff --git a/setup/extensions.py b/setup/extensions.py index a7288260d1..9ac5fa07f4 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -93,6 +93,10 @@ extensions = [ optimize_level=2, ), + Extension('monotonic', + ['calibre/utils/monotonic.c'], + ), + Extension('speedup', ['calibre/utils/speedup.c'], libraries=[] if iswindows else ['m'] diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 41f9b4cdac..92a1985f62 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -135,6 +135,7 @@ class Plugins(collections.Mapping): 'chm_extra', 'icu', 'speedup', + 'monotonic', 'html', 'freetype', 'unrar', diff --git a/src/calibre/srv/tests/loop.py b/src/calibre/srv/tests/loop.py index d836f58346..9be3622008 100644 --- a/src/calibre/srv/tests/loop.py +++ b/src/calibre/srv/tests/loop.py @@ -20,6 +20,7 @@ except ImportError: from calibre.srv.pre_activated import has_preactivated_support from calibre.srv.tests.base import BaseTest, TestServer from calibre.ptempfile import TemporaryDirectory +from calibre.utils.monotonic import monotonic class LoopTest(BaseTest): @@ -209,3 +210,15 @@ class LoopTest(BaseTest): self.ae(r.status, httplib.OK) self.ae(r.read(), b'testbody') self.ae(server.loop.bound_address[1], port) + + def test_monotonic(self): + 'Test the monotonic() clock' + a = monotonic() + b = monotonic() + self.assertGreaterEqual(b, a) + a = monotonic() + time.sleep(0.01) + b = monotonic() + self.assertGreaterEqual(b, a) + self.assertGreaterEqual(b - a, 0.01) + self.assertLessEqual(b - a, 0.02) diff --git a/src/calibre/utils/monotonic.c b/src/calibre/utils/monotonic.c new file mode 100644 index 0000000000..20f03a5cd9 --- /dev/null +++ b/src/calibre/utils/monotonic.c @@ -0,0 +1,85 @@ +/* + * monotonic.c + * Copyright (C) 2015 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#define UNICODE +#include + +/* To millisecond (10^-3) */ +#define SEC_TO_MS 1000 + +/* To microseconds (10^-6) */ +#define MS_TO_US 1000 +#define SEC_TO_US (SEC_TO_MS * MS_TO_US) + +/* To nanoseconds (10^-9) */ +#define US_TO_NS 1000 +#define MS_TO_NS (MS_TO_US * US_TO_NS) +#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS) + +/* Conversion from nanoseconds */ +#define NS_TO_MS (1000 * 1000) +#define NS_TO_US (1000) + +#ifdef _MSC_VER +#include +static LARGE_INTEGER frequency = {0}, ts = {0}; + +/* static PyObject* monotonic(PyObject *self, PyObject *args) { */ +/* return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS); */ +/* } */ + +static PyObject* monotonic(PyObject *self, PyObject *args) { + if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; } + return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart); +} + +#elif defined(__APPLE__) +#include +static mach_timebase_info_data_t timebase = {0}; +static PyObject* monotonic(PyObject *self, PyObject *args) { + return PyFloat_FromDouble(((double)(mach_absolute_time() * timebase.numer) / timebase.denom)/SEC_TO_NS); +} + +#else +#include +static struct timespec ts = {0}; +#ifdef CLOCK_HIGHRES +const static clockid_t clk_id = CLOCK_HIGHRES; +#elif defined(CLOCK_MONOTONIC_RAW) +const static clockid_t clk_id = CLOCK_MONOTONIC_RAW; +#else +const static clockid_t clk_id = CLOCK_MONOTONIC; +#endif +static PyObject* monotonic(PyObject *self, PyObject *args) { + if (clock_gettime(clk_id, &ts) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } + return PyFloat_FromDouble((((double)ts.tv_nsec) / SEC_TO_NS) + (double)ts.tv_sec); +} +#endif + +static PyMethodDef monotonic_methods[] = { + {"monotonic", monotonic, METH_NOARGS, + "monotonic()\n\nReturn a monotonically increasing time value." + }, + + {NULL, NULL, 0, NULL} +}; + +PyMODINIT_FUNC +initmonotonic(void) { + PyObject *m; +#ifdef _MSC_VER + if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } +#endif +#ifdef __APPLE__ + mach_timebase_info(&timebase); +#endif + m = Py_InitModule3("monotonic", monotonic_methods, + "Implementation of time.monotonic() in C for speed" + ); + if (m == NULL) return; +} + diff --git a/src/calibre/utils/monotonic.py b/src/calibre/utils/monotonic.py index 36d6b8f891..da6c88249e 100644 --- a/src/calibre/utils/monotonic.py +++ b/src/calibre/utils/monotonic.py @@ -1,110 +1,9 @@ # vim:fileencoding=utf-8 -from __future__ import division, absolute_import +from calibre.constants import plugins -try: - try: - # >=python-3.3, Unix - from time import clock_gettime - try: - # >={kernel}-sources-2.6.28 - from time import CLOCK_MONOTONIC_RAW as CLOCK_ID - except ImportError: - from time import CLOCK_MONOTONIC as CLOCK_ID # NOQA - - monotonic = lambda: clock_gettime(CLOCK_ID) - - except ImportError: - # >=python-3.3 - from time import monotonic # NOQA - -except ImportError: - import ctypes - import sys - NSEC_PER_SEC = 1e9 - - try: - if sys.platform == 'win32': - # Windows only - perf_frequency = ctypes.c_uint64() - if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(perf_frequency)) == 0: - from time import time as monotonic # noqa - else: - perf_frequency = perf_frequency.value - - def monotonic(): - perf_counter = ctypes.c_uint64() - if ctypes.windll.kernel32.QueryPerformanceCounter(ctypes.byref(perf_counter)) == 0: - raise ctypes.WinError() - return perf_counter.value / perf_frequency - - elif sys.platform == 'darwin': - # Mac OS X - from ctypes.util import find_library - - libc_name = find_library('c') - if not libc_name: - raise OSError - - libc = ctypes.CDLL(libc_name, use_errno=True) - - mach_absolute_time = libc.mach_absolute_time - mach_absolute_time.argtypes = () - mach_absolute_time.restype = ctypes.c_uint64 - - class mach_timebase_info_data_t(ctypes.Structure): - _fields_ = ( - ('numer', ctypes.c_uint32), - ('denom', ctypes.c_uint32), - ) - mach_timebase_info_data_p = ctypes.POINTER(mach_timebase_info_data_t) - - _mach_timebase_info = libc.mach_timebase_info - _mach_timebase_info.argtypes = (mach_timebase_info_data_p,) - _mach_timebase_info.restype = ctypes.c_int - - def mach_timebase_info(): - timebase = mach_timebase_info_data_t() - _mach_timebase_info(ctypes.byref(timebase)) - return (timebase.numer, timebase.denom) - - timebase = mach_timebase_info() - factor = timebase[0] / (timebase[1] * NSEC_PER_SEC) - - def monotonic(): # NOQA - return mach_absolute_time() * factor - else: - # linux only (no librt on OS X) - import os - - # See - CLOCK_MONOTONIC = 1 - CLOCK_MONOTONIC_RAW = 4 - - class timespec(ctypes.Structure): - _fields_ = ( - ('tv_sec', ctypes.c_long), - ('tv_nsec', ctypes.c_long) - ) - tspec = timespec() - - librt = ctypes.CDLL('librt.so.1', use_errno=True) - clock_gettime = librt.clock_gettime - clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)] - - if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(tspec)) == 0: - # >={kernel}-sources-2.6.28 - clock_id = CLOCK_MONOTONIC_RAW - elif clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) == 0: - clock_id = CLOCK_MONOTONIC - else: - raise OSError - - def monotonic(): - if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) != 0: - errno_ = ctypes.get_errno() - raise OSError(errno_, os.strerror(errno_)) - return tspec.tv_sec + tspec.tv_nsec / NSEC_PER_SEC - - except: - from time import time as monotonic # noqa +monotonicp, err = plugins['monotonic'] +if err: + raise RuntimeError('Failed to load the monotonic module with error: ' + err) +monotonic = monotonicp.monotonic +del monotonicp, err