Commit 9c8e01d8 authored by Jason Madden's avatar Jason Madden

Compile greenlet with Cython to make up for most of the lost speed.

We're now only ~2x slower, instead of 10x.
parent 15fc1ecc
...@@ -9,6 +9,7 @@ gevent.*.[ch] ...@@ -9,6 +9,7 @@ gevent.*.[ch]
src/gevent/__pycache__ src/gevent/__pycache__
src/gevent/_semaphore.c src/gevent/_semaphore.c
src/gevent/local.c src/gevent/local.c
src/gevent/greenlet.c
src/gevent/libev/corecext.c src/gevent/libev/corecext.c
src/gevent/libev/corecext.h src/gevent/libev/corecext.h
src/gevent/libev/_corecffi.c src/gevent/libev/_corecffi.c
......
...@@ -90,6 +90,9 @@ ...@@ -90,6 +90,9 @@
a "spawn tree local" mapping. Based on a proposal from PayPal and a "spawn tree local" mapping. Based on a proposal from PayPal and
comments by Mahmoud Hashemi and Kurt Rose. See :issue:`755`. comments by Mahmoud Hashemi and Kurt Rose. See :issue:`755`.
- The :mod:`gevent.greenlet` module is now compiled with Cython to
offset any performance loss due to :issue:`755`.
1.3a1 (2018-01-27) 1.3a1 (2018-01-27)
================== ==================
......
...@@ -223,7 +223,6 @@ latex_documents = [ ...@@ -223,7 +223,6 @@ latex_documents = [
# prevent some stuff from showing up in docs # prevent some stuff from showing up in docs
import socket import socket
import gevent.socket import gevent.socket
del gevent.Greenlet.throw
for item in gevent.socket.__all__[:]: for item in gevent.socket.__all__[:]:
if getattr(gevent.socket, item) is getattr(socket, item, None): if getattr(gevent.socket, item) is getattr(socket, item, None):
gevent.socket.__all__.remove(item) gevent.socket.__all__.remove(item)
...@@ -36,25 +36,7 @@ generated. ...@@ -36,25 +36,7 @@ generated.
.. automethod:: Greenlet.__init__ .. automethod:: Greenlet.__init__
.. attribute:: Greenlet.value
Holds the value returned by the function if the greenlet has
finished successfully. Until then, or if it finished in error, ``None``.
.. tip:: Recall that a greenlet killed with the default
:class:`GreenletExit` is considered to have finished
successfully, and the ``GreenletExit`` exception will be
its value.
.. autoattribute:: Greenlet.exception .. autoattribute:: Greenlet.exception
.. autoattribute:: Greenlet.spawn_tree_locals
:annotation: = {}
.. autoattribute:: Greenlet.spawning_greenlet
:annotation: = weakref.ref()
.. autoattribute:: Greenlet.spawning_stack
:annotation: = <Frame>
.. autoattribute:: Greenlet.spawning_stack_limit
.. automethod:: Greenlet.ready .. automethod:: Greenlet.ready
.. automethod:: Greenlet.successful .. automethod:: Greenlet.successful
.. automethod:: Greenlet.start .. automethod:: Greenlet.start
......
...@@ -4,6 +4,7 @@ from __future__ import print_function ...@@ -4,6 +4,7 @@ from __future__ import print_function
import sys import sys
import os import os
import os.path import os.path
import sysconfig
# setuptools is *required* on Windows # setuptools is *required* on Windows
# (https://bugs.python.org/issue23246) and for PyPy. No reason not to # (https://bugs.python.org/issue23246) and for PyPy. No reason not to
...@@ -59,11 +60,28 @@ LOCAL = Extension(name="gevent.local", ...@@ -59,11 +60,28 @@ LOCAL = Extension(name="gevent.local",
depends=['src/gevent/local.pxd']) depends=['src/gevent/local.pxd'])
LOCAL = cythonize1(LOCAL) LOCAL = cythonize1(LOCAL)
# The sysconfig dir is not enough if we're in a virtualenv
# See https://github.com/pypa/pip/issues/4610
include_dirs = [sysconfig.get_path("include")]
venv_include_dir = os.path.join(sys.prefix, 'include', 'site',
'python' + sysconfig.get_python_version())
venv_include_dir = os.path.abspath(venv_include_dir)
if os.path.exists(venv_include_dir):
include_dirs.append(venv_include_dir)
GREENLET = Extension(name="gevent.greenlet",
sources=["src/gevent/greenlet.py"],
depends=['src/gevent/greenlet.pxd'],
include_dirs=include_dirs)
GREENLET = cythonize1(GREENLET)
EXT_MODULES = [ EXT_MODULES = [
CORE, CORE,
ARES, ARES,
SEMAPHORE, SEMAPHORE,
LOCAL, LOCAL,
GREENLET,
] ]
LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi' LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi'
...@@ -91,6 +109,7 @@ if PYPY: ...@@ -91,6 +109,7 @@ if PYPY:
setup_requires = [] setup_requires = []
EXT_MODULES.remove(CORE) EXT_MODULES.remove(CORE)
EXT_MODULES.remove(LOCAL) EXT_MODULES.remove(LOCAL)
EXT_MODULES.remove(GREENLET)
EXT_MODULES.remove(SEMAPHORE) EXT_MODULES.remove(SEMAPHORE)
# By building the semaphore with Cython under PyPy, we get # By building the semaphore with Cython under PyPy, we get
# atomic operations (specifically, exiting/releasing), at the # atomic operations (specifically, exiting/releasing), at the
......
# cython: auto_pickle=False
cimport cython
cdef extern from "greenlet/greenlet.h":
ctypedef class greenlet.greenlet [object PyGreenlet]:
pass
cdef class SpawnedLink:
cdef public object callback
@cython.final
cdef class SuccessSpawnedLink(SpawnedLink):
pass
@cython.final
cdef class FailureSpawnedLink(SpawnedLink):
pass
@cython.final
@cython.internal
cdef class _Frame:
cdef readonly object f_code
cdef readonly int f_lineno
cdef public _Frame f_back
cdef class Greenlet(greenlet):
cdef readonly object value
cdef readonly args
cdef readonly object spawning_greenlet
cdef public dict spawn_tree_locals
cdef readonly _Frame spawning_stack
# A test case reads these, otherwise they would
# be private
cdef readonly tuple _exc_info
cdef readonly list _links
cdef object _notifier
cdef object _start_event
cdef dict _kwargs
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 __cancel_start(self)
cdef _report_result(self, object result)
cdef _report_error(self, tuple exc_info)
@cython.final
@cython.internal
cdef class _dummy_event:
cdef readonly bint pending
cdef readonly bint active
cpdef stop(self)
cpdef start(self, cb)
cpdef close(self)
cdef _dummy_event _cancelled_start_event
cdef _dummy_event _start_completed_event
@cython.locals(diehards=list)
cdef _killall3(list greenlets, object exception, object waiter)
cdef _killall(list greenlets, object exception)
@cython.locals(done=list)
cpdef joinall(greenlets, timeout=*, raise_error=*, count=*)
This diff is collapsed.
...@@ -374,7 +374,8 @@ class TestStuff(greentest.TestCase): ...@@ -374,7 +374,8 @@ class TestStuff(greentest.TestCase):
link(results.listener2) link(results.listener2)
link(results.listener3) link(results.listener3)
sleep(DELAY * 10) sleep(DELAY * 10)
assert results.results == [5], results.results self.assertEqual([5], results.results)
def test_multiple_listeners_error_unlink_Greenlet_link(self): def test_multiple_listeners_error_unlink_Greenlet_link(self):
p = gevent.spawn(lambda: 5) p = gevent.spawn(lambda: 5)
...@@ -515,14 +516,14 @@ class TestBasic(greentest.TestCase): ...@@ -515,14 +516,14 @@ class TestBasic(greentest.TestCase):
assert g.exception is None assert g.exception is None
gevent.sleep(0.001) gevent.sleep(0.001)
assert g self.assertTrue(g)
assert not g.dead self.assertFalse(g.dead, g)
assert g.started self.assertTrue(g.started, g)
assert not g.ready() self.assertFalse(g.ready(), g)
assert not g.successful() self.assertFalse(g.successful(), g)
assert g.value is None self.assertIsNone(g.value, g)
assert g.exception is None self.assertIsNone(g.exception, g)
assert not link_test self.assertFalse(link_test)
gevent.sleep(0.02) gevent.sleep(0.02)
assert not g assert not g
......
...@@ -11,7 +11,7 @@ import greentest ...@@ -11,7 +11,7 @@ import greentest
import gevent import gevent
from gevent import util from gevent import util
@greentest.skipOnPyPy("5.10.x is *very* slow formatting stacks")
class TestFormat(greentest.TestCase): class TestFormat(greentest.TestCase):
def test_basic(self): def test_basic(self):
......
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