Speedup monotonic() by eliminating the ctypes overhead

This commit is contained in:
Kovid Goyal 2015-10-28 14:39:49 +05:30
parent af07373638
commit 343574245f
5 changed files with 109 additions and 107 deletions

View File

@ -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']

View File

@ -135,6 +135,7 @@ class Plugins(collections.Mapping):
'chm_extra',
'icu',
'speedup',
'monotonic',
'html',
'freetype',
'unrar',

View File

@ -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)

View File

@ -0,0 +1,85 @@
/*
* monotonic.c
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define UNICODE
#include <Python.h>
/* 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 <Windows.h>
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 <mach/mach_time.h>
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 <time.h>
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;
}

View File

@ -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 <bits/time.h>
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