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 ...@@ -18,12 +18,12 @@ from gevent import _tracer as monitor
N = 1000 N = 1000
@contextlib.contextmanager @contextlib.contextmanager
def tracer(func): def tracer(cls, *args):
greenlet.settrace(func) inst = cls(*args)
try: try:
yield yield
finally: finally:
greenlet.settrace(None) inst.kill()
def _run(loops): def _run(loops):
...@@ -58,25 +58,28 @@ def bench_trivial_tracer(loops): ...@@ -58,25 +58,28 @@ def bench_trivial_tracer(loops):
def trivial(_event, _args): def trivial(_event, _args):
return return
with tracer(trivial): greenlet.settrace(trivial)
try:
return _run(loops) return _run(loops)
finally:
greenlet.settrace(None)
def bench_monitor_tracer(loops): def bench_monitor_tracer(loops):
with tracer(monitor.GreenletTracer()): with tracer(monitor.GreenletTracer):
return _run(loops) return _run(loops)
def bench_hub_switch_tracer(loops): def bench_hub_switch_tracer(loops):
# use current as the hub, since tracer fires # use current as the hub, since tracer fires
# when we switch into that greenlet # when we switch into that greenlet
with tracer(monitor.HubSwitchTracer(gevent.getcurrent(), 1)): with tracer(monitor.HubSwitchTracer, gevent.getcurrent(), 1):
return _run(loops) return _run(loops)
def bench_max_switch_tracer(loops): def bench_max_switch_tracer(loops):
# use object() as the hub, since tracer fires # use object() as the hub, since tracer fires
# when switch into something that's *not* the hub # when switch into something that's *not* the hub
with tracer(monitor.MaxSwitchTracer(object, 1)): with tracer(monitor.MaxSwitchTracer, object, 1):
return _run(loops) return _run(loops)
def main(): 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. # 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 from __future__ import print_function, absolute_import, division
import sys import sys
...@@ -19,33 +20,40 @@ __all__ = [ ...@@ -19,33 +20,40 @@ __all__ = [
'MaxSwitchTracer', 'MaxSwitchTracer',
] ]
# Recall these classes are cython compiled, so
# class variable declarations are bad.
class GreenletTracer(object):
# A counter, incremented by the greenlet trace function class GreenletTracer(object):
# we install on every greenlet switch. This is reset when the def __init__(self):
# periodic monitoring thread runs. # A counter, incremented by the greenlet trace function
greenlet_switch_counter = 0 # we install on every greenlet switch. This is reset when the
# periodic monitoring thread runs.
# The greenlet last switched to. self.greenlet_switch_counter = 0
active_greenlet = None
# The trace function that was previously installed, # The greenlet last switched to.
# if any. self.active_greenlet = None
previous_trace_function = None
def __init__(self): # The trace function that was previously installed,
# if any.
# 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) prev_trace = settrace(self)
self.previous_trace_function = prev_trace 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. # Must be called in the monitored thread.
settrace(self.previous_trace_function) if not self._killed:
self.previous_trace_function = None self._killed = True
# Become a no-op settrace(self.previous_trace_function)
self.kill = lambda: None self.previous_trace_function = None
def __call__(self, event, args): def _trace(self, event, args):
# This function runs in the thread we are monitoring. # This function runs in the thread we are monitoring.
self.greenlet_switch_counter += 1 self.greenlet_switch_counter += 1
if event in ('switch', 'throw'): if event in ('switch', 'throw'):
...@@ -57,6 +65,9 @@ class GreenletTracer(object): ...@@ -57,6 +65,9 @@ class GreenletTracer(object):
if self.previous_trace_function is not None: if self.previous_trace_function is not None:
self.previous_trace_function(event, args) self.previous_trace_function(event, args)
def __call__(self, event, args):
return self._trace(event, args)
def did_block_hub(self, hub): def did_block_hub(self, hub):
# Check to see if we have blocked since the last call to this # 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), # method. Returns a true value if we blocked (not in the hub),
...@@ -107,13 +118,14 @@ class GreenletTracer(object): ...@@ -107,13 +118,14 @@ class GreenletTracer(object):
return report return report
class _HubTracer(GreenletTracer): class _HubTracer(GreenletTracer):
def __init__(self, hub, max_blocking_time): def __init__(self, hub, max_blocking_time):
GreenletTracer.__init__(self) GreenletTracer.__init__(self)
self.max_blocking_time = max_blocking_time self.max_blocking_time = max_blocking_time
self.hub = hub self.hub = hub
def kill(self): # pylint:disable=method-hidden def kill(self):
self.hub = None self.hub = None
GreenletTracer.kill(self) GreenletTracer.kill(self)
...@@ -121,10 +133,12 @@ class _HubTracer(GreenletTracer): ...@@ -121,10 +133,12 @@ class _HubTracer(GreenletTracer):
class HubSwitchTracer(_HubTracer): class HubSwitchTracer(_HubTracer):
# A greenlet tracer that records the last time we switched *into* the hub. # 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): def _trace(self, event, args):
GreenletTracer.__call__(self, event, args) GreenletTracer._trace(self, event, args)
if self.active_greenlet is self.hub: if self.active_greenlet is self.hub:
self.last_entered_hub = perf_counter() self.last_entered_hub = perf_counter()
...@@ -137,15 +151,14 @@ class MaxSwitchTracer(_HubTracer): ...@@ -137,15 +151,14 @@ class MaxSwitchTracer(_HubTracer):
# A greenlet tracer that records the maximum time between switches, # A greenlet tracer that records the maximum time between switches,
# not including time spent in the hub. # not including time spent in the hub.
max_blocking = 0
def __init__(self, hub, max_blocking_time): def __init__(self, hub, max_blocking_time):
_HubTracer.__init__(self, hub, max_blocking_time) _HubTracer.__init__(self, hub, max_blocking_time)
self.last_switch = perf_counter() self.last_switch = perf_counter()
self.max_blocking = 0
def __call__(self, event, args): def _trace(self, event, args):
old_active = self.active_greenlet 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 old_active is not self.hub and old_active is not None:
# If we're switching out of the hub, the blocking # If we're switching out of the hub, the blocking
# time doesn't count. # time doesn't count.
......
...@@ -217,9 +217,9 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread, ...@@ -217,9 +217,9 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
self.assertEqual(gettrace(), self.pmt._greenlet_tracer) self.assertEqual(gettrace(), self.pmt._greenlet_tracer)
self.assertIs(self.pmt._greenlet_tracer.previous_trace_function, f) 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): def test__greenlet_tracer(self):
self.assertEqual(0, self.pmt._greenlet_tracer.greenlet_switch_counter) self.assertEqual(0, self.pmt._greenlet_tracer.greenlet_switch_counter)
...@@ -236,7 +236,7 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread, ...@@ -236,7 +236,7 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
self.assertIs(target, self.pmt._greenlet_tracer.active_greenlet) self.assertIs(target, self.pmt._greenlet_tracer.active_greenlet)
# Unknown event removes 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.assertEqual(3, self.pmt._greenlet_tracer.greenlet_switch_counter)
self.assertIsNone(self.pmt._greenlet_tracer.active_greenlet) 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