Commit aa911ed0 authored by Jason Madden's avatar Jason Madden

Compile the monitor greenlet tracer with Cython.

This makes things 54% faster. In fact, the monitor tracer is now faster than a trivial tracer implemented in python (settrace(lambda e, a: None)).

+-------------------+-----------------+-----------------------------+
| Benchmark         | 37_bench_tracer | 37_bench_tracer_cython_opt3 |
+===================+=================+=============================+
| monitor tracer    | 1.62 us         | 739 ns: 2.20x faster (-54%) |
+-------------------+-----------------+-----------------------------+
| max switch tracer | 3.06 us         | 874 ns: 3.50x faster (-71%) |
+-------------------+-----------------+-----------------------------+
| hub switch tracer | 2.16 us         | 815 ns: 2.66x faster (-62%) |
+-------------------+-----------------+-----------------------------+

Not significant (2): no tracer; trivial tracer
parent 4231079a
......@@ -18,12 +18,12 @@ from gevent import _tracer as monitor
N = 1000
@contextlib.contextmanager
def tracer(func):
greenlet.settrace(func)
def tracer(cls, *args):
inst = cls(*args)
try:
yield
finally:
greenlet.settrace(None)
inst.kill()
def _run(loops):
......@@ -58,25 +58,28 @@ def bench_trivial_tracer(loops):
def trivial(_event, _args):
return
with tracer(trivial):
greenlet.settrace(trivial)
try:
return _run(loops)
finally:
greenlet.settrace(None)
def bench_monitor_tracer(loops):
with tracer(monitor.GreenletTracer()):
with tracer(monitor.GreenletTracer):
return _run(loops)
def bench_hub_switch_tracer(loops):
# use current as the hub, since tracer fires
# when we switch into that greenlet
with tracer(monitor.HubSwitchTracer(gevent.getcurrent(), 1)):
with tracer(monitor.HubSwitchTracer, gevent.getcurrent(), 1):
return _run(loops)
def bench_max_switch_tracer(loops):
# use object() as the hub, since tracer fires
# when switch into something that's *not* the hub
with tracer(monitor.MaxSwitchTracer(object, 1)):
with tracer(monitor.MaxSwitchTracer, object, 1):
return _run(loops)
def main():
......
cimport cython
cdef sys
cdef traceback
cdef settrace
cdef getcurrent
cdef format_run_info
cdef perf_counter
cdef gmctime
cdef class GreenletTracer:
cpdef readonly object active_greenlet
cpdef readonly object previous_trace_function
cpdef readonly Py_ssize_t greenlet_switch_counter
cdef bint _killed
cpdef _trace(self, str event, tuple args)
@cython.locals(did_switch=bint)
cpdef did_block_hub(self, hub)
cpdef kill(self)
@cython.internal
cdef class _HubTracer(GreenletTracer):
cpdef readonly object hub
cpdef readonly double max_blocking_time
cdef class HubSwitchTracer(_HubTracer):
cpdef readonly double last_entered_hub
cdef class MaxSwitchTracer(_HubTracer):
cpdef readonly double max_blocking
cpdef readonly double last_switch
@cython.locals(switched_at=double)
cpdef _trace(self, str event, tuple args)
# Copyright (c) 2018 gevent. See LICENSE for details.
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
from __future__ import print_function, absolute_import, division
import sys
......@@ -19,33 +20,40 @@ __all__ = [
'MaxSwitchTracer',
]
# Recall these classes are cython compiled, so
# class variable declarations are bad.
class GreenletTracer(object):
class GreenletTracer(object):
def __init__(self):
# A counter, incremented by the greenlet trace function
# we install on every greenlet switch. This is reset when the
# periodic monitoring thread runs.
greenlet_switch_counter = 0
self.greenlet_switch_counter = 0
# The greenlet last switched to.
active_greenlet = None
self.active_greenlet = None
# The trace function that was previously installed,
# if any.
previous_trace_function = None
def __init__(self):
# NOTE: Calling a class instance is cheaper than
# calling a bound method (at least when compiled with cython)
# even when it redirects to another function.
prev_trace = settrace(self)
self.previous_trace_function = prev_trace
def kill(self): # pylint:disable=method-hidden
self._killed = False
def kill(self):
# Must be called in the monitored thread.
if not self._killed:
self._killed = True
settrace(self.previous_trace_function)
self.previous_trace_function = None
# Become a no-op
self.kill = lambda: None
def __call__(self, event, args):
def _trace(self, event, args):
# This function runs in the thread we are monitoring.
self.greenlet_switch_counter += 1
if event in ('switch', 'throw'):
......@@ -57,6 +65,9 @@ class GreenletTracer(object):
if self.previous_trace_function is not None:
self.previous_trace_function(event, args)
def __call__(self, event, args):
return self._trace(event, args)
def did_block_hub(self, hub):
# Check to see if we have blocked since the last call to this
# method. Returns a true value if we blocked (not in the hub),
......@@ -107,13 +118,14 @@ class GreenletTracer(object):
return report
class _HubTracer(GreenletTracer):
def __init__(self, hub, max_blocking_time):
GreenletTracer.__init__(self)
self.max_blocking_time = max_blocking_time
self.hub = hub
def kill(self): # pylint:disable=method-hidden
def kill(self):
self.hub = None
GreenletTracer.kill(self)
......@@ -121,10 +133,12 @@ class _HubTracer(GreenletTracer):
class HubSwitchTracer(_HubTracer):
# A greenlet tracer that records the last time we switched *into* the hub.
last_entered_hub = 0
def __init__(self, hub, max_blocking_time):
_HubTracer.__init__(self, hub, max_blocking_time)
self.last_entered_hub = 0
def __call__(self, event, args):
GreenletTracer.__call__(self, event, args)
def _trace(self, event, args):
GreenletTracer._trace(self, event, args)
if self.active_greenlet is self.hub:
self.last_entered_hub = perf_counter()
......@@ -137,15 +151,14 @@ class MaxSwitchTracer(_HubTracer):
# A greenlet tracer that records the maximum time between switches,
# not including time spent in the hub.
max_blocking = 0
def __init__(self, hub, max_blocking_time):
_HubTracer.__init__(self, hub, max_blocking_time)
self.last_switch = perf_counter()
self.max_blocking = 0
def __call__(self, event, args):
def _trace(self, event, args):
old_active = self.active_greenlet
GreenletTracer.__call__(self, event, args)
GreenletTracer._trace(self, event, args)
if old_active is not self.hub and old_active is not None:
# If we're switching out of the hub, the blocking
# time doesn't count.
......
......@@ -217,9 +217,9 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
self.assertEqual(gettrace(), self.pmt._greenlet_tracer)
self.assertIs(self.pmt._greenlet_tracer.previous_trace_function, f)
self.pmt._greenlet_tracer('event', 'args')
self.pmt._greenlet_tracer('event', ('args',))
self.assertEqual([('event', 'args')], called)
self.assertEqual([('event', ('args',))], called)
def test__greenlet_tracer(self):
self.assertEqual(0, self.pmt._greenlet_tracer.greenlet_switch_counter)
......@@ -236,7 +236,7 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
self.assertIs(target, self.pmt._greenlet_tracer.active_greenlet)
# Unknown event removes active greenlet
self.pmt._greenlet_tracer('unknown', self)
self.pmt._greenlet_tracer('unknown', ())
self.assertEqual(3, self.pmt._greenlet_tracer.greenlet_switch_counter)
self.assertIsNone(self.pmt._greenlet_tracer.active_greenlet)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment