Skip to content

Commit

Permalink
More cython declarations for more speed.
Browse files Browse the repository at this point in the history
Small improvements to creating the objects, somewhat larger
improvements to joining.

From bench_spawn on 3.6.4 before any work:

//gevent36/bin/python ./bench_spawn.py geventpool --ignore-import-errors
using gevent from //src/gevent/__init__.py
spawning: 8.71 microseconds per greenlet
sleep(0): 16.99 microseconds per greenlet
 joining: 7.34 microseconds per greenlet

//gevent36/bin/python ./bench_spawn.py --with-kwargs geventpool --ignore-import-errors
using gevent from //src/gevent/__init__.py
spawning: 10.25 microseconds per greenlet
sleep(0): 18.89 microseconds per greenlet
 joining: 7.14 microseconds per greenlet

And now:

//gevent36/bin/python ./bench_spawn.py geventpool --ignore-import-errors
using gevent from //src/gevent/__init__.py
spawning: 11.56 microseconds per greenlet
sleep(0): 16.76 microseconds per greenlet
 joining: 5.74 microseconds per greenlet

//gevent36/bin/python ./bench_spawn.py --with-kwargs geventpool --ignore-import-errors
using gevent from //src/gevent/__init__.py
spawning: 11.49 microseconds per greenlet
sleep(0): 17.16 microseconds per greenlet
 joining: 5.31 microseconds per greenlet
  • Loading branch information
jamadden committed Feb 22, 2018
1 parent 5e6c1bc commit 0c13aff
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 36 deletions.
40 changes: 38 additions & 2 deletions src/gevent/greenlet.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

cimport cython


cdef extern from "greenlet/greenlet.h":

ctypedef class greenlet.greenlet [object PyGreenlet]:
pass


cdef class SpawnedLink:
cdef public object callback

Expand All @@ -29,6 +27,15 @@ cdef class _Frame:
cdef public _Frame f_back


@cython.final
@cython.locals(
previous=_Frame,
first=_Frame,
next_frame=_Frame)
cdef _Frame _extract_stack(int limit, _Frame f_back)



cdef class Greenlet(greenlet):
cdef readonly object value
cdef readonly args
Expand All @@ -44,17 +51,46 @@ cdef class Greenlet(greenlet):
cdef dict _kwargs

cpdef bint has_links(self)
cpdef join(self, timeout=*)
cpdef bint ready(self)
cpdef bint successful(self)
cpdef rawlink(self, object callback)

cdef bint __started_but_aborted(self)
cdef bint __start_cancelled_by_kill(self)
cdef bint __start_pending(self)
cdef bint __never_started_or_killed(self)
cdef bint __start_completed(self)
cdef __handle_death_before_start(self, tuple args)

cdef __cancel_start(self)

cdef _report_result(self, object result)
cdef _report_error(self, tuple exc_info)
# This is used as the target of a callback
# from the loop, and so needs to be a cpdef
cpdef _notify_links(self)
# IMapUnordered greenlets in pools need to access this
# method
cpdef _raise_exception(self)

# Declare a bunch of imports as cdefs so they can
# be accessed directly as static vars without
# doing a module global lookup. This is especially important
# for spawning greenlets.
cdef _greenlet__init__
cdef get_hub
cdef wref
cdef getcurrent

cdef Timeout
cdef dump_traceback
cdef load_traceback
cdef Waiter
cdef wait
cdef iwait
cdef reraise
cdef InvalidSwitchError


@cython.final
Expand Down
79 changes: 45 additions & 34 deletions src/gevent/greenlet.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
# cython: auto_pickle=False
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
from __future__ import absolute_import

import sys
Expand Down Expand Up @@ -102,6 +102,28 @@ def __init__(self, f_code, f_lineno):

f_globals = property(lambda _self: None)

def _extract_stack(limit, f_back):
previous = None
frame = sys._getframe()
first = None

first = previous = _Frame(frame.f_code, frame.f_lineno)
limit -= 1
frame = frame.f_back

while limit and frame is not None:
limit -= 1
next_frame = _Frame(frame.f_code, frame.f_lineno)
previous.f_back = next_frame
previous = next_frame
frame = frame.f_back

previous.f_back = f_back
return first


_greenlet__init__ = greenlet.__init__

class Greenlet(greenlet):
"""
A light-weight cooperatively-scheduled execution unit.
Expand Down Expand Up @@ -193,13 +215,13 @@ def __init__(self, run=None, *args, **kwargs): # pylint:disable=keyword-arg-befo
# PyPy2 5.10.0: Mean +- std dev: 3.24 us +- 0.17 us -> 1.5x

# Compiling with Cython gets us to these numbers:
# 3.6.4 : Mean +- std dev: 4.36 us +- 0.23 us
# 2.7.12 : Mean +- std dev: 3.81 us +- 0.15 us
# PyPy2 5.10.0 : Mean +- std dev: 3.63 us +- 0.22 us
# 3.6.4 : Mean +- std dev: 3.63 us +- 0.14 us
# 2.7.14 : Mean +- std dev: 3.37 us +- 0.20 us
# PyPy2 5.10.0 : Mean +- std dev: 4.44 us +- 0.28 us


#greenlet.__init__(self, None, get_hub())
super(Greenlet, self).__init__(None, get_hub())
_greenlet__init__(self, None, get_hub())
#super(Greenlet, self).__init__(None, get_hub())

if run is not None:
self._run = run
Expand All @@ -220,39 +242,28 @@ def __init__(self, run=None, *args, **kwargs): # pylint:disable=keyword-arg-befo
self.args = args
self._kwargs = kwargs
self.value = None
self._exc_info = ()
self._notifier = None


self._links = []

# Initial state: None.
# Completed successfully: (None, None, None)
# Failed with exception: (t, v, dump_traceback(tb)))
self._exc_info = None

spawner = getcurrent()
self.spawning_greenlet = wref(spawner)
try:
self.spawn_tree_locals = spawner.spawn_tree_locals
except AttributeError:
self.spawn_tree_locals = {}
if spawner.parent:
if spawner.parent is not None:
# The main greenlet has no parent.
# Its children get separate locals.
spawner.spawn_tree_locals = self.spawn_tree_locals


frame = sys._getframe()
previous = None
limit = self.spawning_stack_limit
while limit and frame:
limit -= 1
next_frame = _Frame(frame.f_code, frame.f_lineno)
if previous:
previous.f_back = next_frame
else:
self.spawning_stack = next_frame
previous = next_frame

frame = frame.f_back

previous.f_back = getattr(spawner, 'spawning_stack', None)
self.spawning_stack = _extract_stack(self.spawning_stack_limit,
getattr(spawner, 'spawning_stack', None))


@property
Expand All @@ -268,7 +279,7 @@ def loop(self):
return self.parent.loop

def __nonzero__(self):
return self._start_event is not None and self._exc_info == ()
return self._start_event is not None and self._exc_info is None
try:
__bool__ = __nonzero__ # Python 3
except NameError: # pragma: no cover
Expand Down Expand Up @@ -325,9 +336,9 @@ def __cancel_start(self):
self._start_event.stop()
self._start_event.close()

def __handle_death_before_start(self, *args):
def __handle_death_before_start(self, args):
# args is (t, v, tb) or simply t or v
if self._exc_info == () and self.dead:
if self._exc_info is None and self.dead:
# the greenlet was never switched to before and it will never be, _report_error was not called
# the result was not set and the links weren't notified. let's do it here.
# checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet
Expand Down Expand Up @@ -357,7 +368,7 @@ def ready(self):
This function is only guaranteed to return true or false *values*, not
necessarily the literal constants ``True`` or ``False``.
"""
return self.dead or self._exc_info
return self.dead or self._exc_info is not None

def successful(self):
"""
Expand All @@ -371,7 +382,7 @@ def successful(self):
.. note:: This function is only guaranteed to return true or false *values*,
not necessarily the literal constants ``True`` or ``False``.
"""
return self._exc_info and self._exc_info[1] is None
return self._exc_info is not None and self._exc_info[1] is None

def __repr__(self):
classname = self.__class__.__name__
Expand Down Expand Up @@ -411,7 +422,7 @@ def exception(self):
Holds the exception instance raised by the function if the
greenlet has finished with an error. Otherwise ``None``.
"""
return self._exc_info[1] if self._exc_info else None
return self._exc_info[1] if self._exc_info is not None else None

@property
def exc_info(self):
Expand All @@ -424,7 +435,7 @@ def exc_info(self):
.. versionadded:: 1.1
"""
e = self._exc_info
if e and e[0] is not None:
if e is not None and e[0] is not None:
return (e[0], e[1], load_traceback(e[2]))

def throw(self, *args):
Expand All @@ -447,7 +458,7 @@ def throw(self, *args):
# LoopExit.
greenlet.throw(self, *args)
finally:
self.__handle_death_before_start(*args)
self.__handle_death_before_start(args)

def start(self):
"""Schedule the greenlet to run in this loop iteration"""
Expand Down Expand Up @@ -544,7 +555,7 @@ def kill(self, exception=GreenletExit, block=True, timeout=None):
self.__cancel_start()

if self.dead:
self.__handle_death_before_start(exception)
self.__handle_death_before_start((exception,))
else:
waiter = Waiter() if block else None
self.parent.loop.run_callback(_kill, self, exception, waiter)
Expand Down

0 comments on commit 0c13aff

Please sign in to comment.