Commit b1125a79 authored by Jason Madden's avatar Jason Madden

Speed of gevent.local.local more, especially for subclasses (2-3x faster)

Do this with some cython tricks and some caching of type attributes.
Cython 0.28 compiles this code to be quite a bit faster than Cython
0.27 does (the below uses 0.28), but it's still a win on both.

The type caching could potentially be a compatibility issue, but in
practice I suspect it won't be.

Benchmarks on 3.6:

+--------------------+------------------+-----------------------------+
| Benchmark          | 3.6              | 3.6 This Branch             |
+====================+==================+=============================+
| getattr gevent     | 190 ns           | 158 ns: 1.20x faster (-17%) |
+--------------------+------------------+-----------------------------+
| setattr gevent     | 180 ns           | 165 ns: 1.09x faster (-8%)  |
+--------------------+------------------+-----------------------------+
| getattr gevent sub | 540 ns           | 175 ns: 3.09x faster (-68%) |
+--------------------+------------------+-----------------------------+
| setattr gevent sub | 528 ns           | 179 ns: 2.95x faster (-66%) |
+--------------------+------------------+-----------------------------+
| setattr native     | 80.8 ns          | 78.8 ns: 1.03x faster (-2%) |
+--------------------+------------------+-----------------------------+

Not significant (3): getattr native; getattr native sub; setattr native sub

Benchmarks on 2.7:

+--------------------+------------------+-----------------------------+
| Benchmark          | local_27_master2 | local_27_tweak2             |
+====================+==================+=============================+
| getattr gevent     | 162 ns           | 158 ns: 1.03x faster (-3%)  |
+--------------------+------------------+-----------------------------+
| setattr gevent     | 173 ns           | 165 ns: 1.04x faster (-4%)  |
+--------------------+------------------+-----------------------------+
| getattr gevent sub | 471 ns           | 181 ns: 2.61x faster (-62%) |
+--------------------+------------------+-----------------------------+
| setattr gevent sub | 462 ns           | 190 ns: 2.44x faster (-59%) |
+--------------------+------------------+-----------------------------+
| getattr native     | 87.4 ns          | 89.7 ns: 1.03x slower (+3%) |
+--------------------+------------------+-----------------------------+
| setattr native     | 133 ns           | 110 ns: 1.20x faster (-17%) |
+--------------------+------------------+-----------------------------+
| getattr native sub | 101 ns           | 97.9 ns: 1.03x faster (-3%) |
+--------------------+------------------+-----------------------------+
| setattr native sub | 118 ns           | 111 ns: 1.06x faster (-6%)  |
+--------------------+------------------+-----------------------------+

PyPy unfortunately shows as a little slower, though I'm not sure I
fully believe that:

+--------------------+-------------------+-----------------------------+
| Benchmark          | local_pypy_master | local_pypy_tweak2           |
+====================+===================+=============================+
| getattr gevent     | 135 ns            | 157 ns: 1.16x slower (+16%) |
+--------------------+-------------------+-----------------------------+
| setattr gevent     | 126 ns            | 162 ns: 1.28x slower (+28%) |
+--------------------+-------------------+-----------------------------+
| getattr gevent sub | 150 ns            | 175 ns: 1.17x slower (+17%) |
+--------------------+-------------------+-----------------------------+
| setattr gevent sub | 153 ns            | 183 ns: 1.20x slower (+20%) |
+--------------------+-------------------+-----------------------------+
| getattr native     | 0.18 ns           | 0.19 ns: 1.05x slower (+5%) |
+--------------------+-------------------+-----------------------------+
| setattr native     | 0.18 ns           | 0.20 ns: 1.06x slower (+6%) |
+--------------------+-------------------+-----------------------------+
| getattr native sub | 0.18 ns           | 0.19 ns: 1.05x slower (+5%) |
+--------------------+-------------------+-----------------------------+
| setattr native sub | 0.19 ns           | 0.18 ns: 1.06x faster (-6%) |
+--------------------+-------------------+-----------------------------+
parent d9602bf8
......@@ -103,6 +103,11 @@
Hashemi and Kurt Rose. See :issue:`755`. As always, feedback is
appreciated.
- Simple subclasses of ``gevent.local.local`` now have the same
(substantially improved) performance characteristics of plain
``gevent.local.local`` itself. If there are any compatibility
problems, please open issues.
1.3a1 (2018-01-27)
==================
......
......@@ -55,11 +55,6 @@ SEMAPHORE = Extension(name="gevent._semaphore",
depends=['src/gevent/_semaphore.pxd'])
SEMAPHORE = cythonize1(SEMAPHORE)
LOCAL = Extension(name="gevent.local",
sources=["src/gevent/local.py"],
depends=['src/gevent/local.pxd'])
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")]
......@@ -70,6 +65,13 @@ if os.path.exists(venv_include_dir):
include_dirs.append(venv_include_dir)
LOCAL = Extension(name="gevent.local",
sources=["src/gevent/local.py"],
depends=['src/gevent/local.pxd'],
include_dirs=include_dirs)
LOCAL = cythonize1(LOCAL)
GREENLET = Extension(name="gevent.greenlet",
sources=[
"src/gevent/greenlet.py",
......
# -*- coding: utf-8 -*-
# Copyright 2018 gevent contributors. See LICENSE for details.
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
from __future__ import absolute_import
from __future__ import division
......
......@@ -2,11 +2,30 @@
cimport cython
from gevent._ident cimport IdentRegistry
cdef bint _greenlet_imported
cdef bint _PYPY
cdef extern from "greenlet/greenlet.h":
ctypedef class greenlet.greenlet [object PyGreenlet]:
pass
ctypedef class greenlet.greenlet [object PyGreenlet]:
pass
# These are actually macros and so much be included
# (defined) in each .pxd, as are the two functions
# that call them.
greenlet PyGreenlet_GetCurrent()
void PyGreenlet_Import()
cdef inline greenlet getcurrent():
return PyGreenlet_GetCurrent()
cdef inline void greenlet_init():
global _greenlet_imported
if not _greenlet_imported:
PyGreenlet_Import()
_greenlet_imported = True
cdef void _init()
cdef class SpawnedLink:
cdef public object callback
......@@ -87,7 +106,6 @@ cdef class Greenlet(greenlet):
cdef _greenlet__init__
cdef get_hub
cdef wref
cdef getcurrent
cdef Timeout
cdef dump_traceback
......
......@@ -7,9 +7,7 @@ from weakref import ref as wref
from greenlet import greenlet
from greenlet import getcurrent
from gevent._compat import PYPY
from gevent._compat import reraise
from gevent._tblib import dump_traceback
from gevent._tblib import load_traceback
......@@ -21,6 +19,9 @@ from gevent.hub import iwait
from gevent.hub import wait
from gevent.timeout import Timeout
_PYPY = hasattr(sys, 'pypy_version_info')
__all__ = [
'Greenlet',
'joinall',
......@@ -28,7 +29,15 @@ __all__ = [
]
if PYPY:
# In Cython, we define these as 'cdef inline' functions. The
# compilation unit cannot have a direct assignment to them (import
# is assignment) without generating a 'lvalue is not valid target'
# error.
locals()['getcurrent'] = __import__('greenlet').getcurrent
locals()['greenlet_init'] = lambda: None
if _PYPY:
import _continuation # pylint:disable=import-error
_continulet = _continuation.continulet
......@@ -254,7 +263,7 @@ class Greenlet(greenlet):
# Failed with exception: (t, v, dump_traceback(tb)))
self._exc_info = None
spawner = getcurrent()
spawner = getcurrent() # pylint:disable=undefined-variable
self.spawning_greenlet = wref(spawner)
try:
self.spawn_tree_locals = spawner.spawn_tree_locals
......@@ -317,7 +326,7 @@ class Greenlet(greenlet):
### Lifecycle
if PYPY:
if _PYPY:
# oops - pypy's .dead relies on __nonzero__ which we overriden above
@property
def dead(self):
......@@ -618,7 +627,7 @@ class Greenlet(greenlet):
if not block:
raise Timeout()
switch = getcurrent().switch
switch = getcurrent().switch # pylint:disable=undefined-variable
self.rawlink(switch)
try:
t = Timeout._start_new_or_dummy(timeout)
......@@ -652,7 +661,7 @@ class Greenlet(greenlet):
if self.ready():
return
switch = getcurrent().switch
switch = getcurrent().switch # pylint:disable=undefined-variable
self.rawlink(switch)
try:
t = Timeout._start_new_or_dummy(timeout)
......@@ -912,3 +921,8 @@ def killall(greenlets, exception=GreenletExit, block=True, timeout=None):
t.cancel()
else:
loop.run_callback(_killall, greenlets, exception)
def _init():
greenlet_init() # pylint:disable=undefined-variable
_init()
......@@ -2,6 +2,37 @@
cimport cython
cdef bint _PYPY
cdef ref
cdef copy
cdef object _marker
cdef bint _greenlet_imported
cdef extern from "greenlet/greenlet.h":
ctypedef class greenlet.greenlet [object PyGreenlet]:
pass
# These are actually macros and so much be included
# (defined) in each .pxd, as are the two functions
# that call them.
greenlet PyGreenlet_GetCurrent()
void PyGreenlet_Import()
cdef inline greenlet getcurrent():
return PyGreenlet_GetCurrent()
cdef inline void greenlet_init():
global _greenlet_imported
if not _greenlet_imported:
PyGreenlet_Import()
_greenlet_imported = True
cdef void _init()
@cython.final
@cython.internal
cdef class _wrefdict(dict):
......@@ -9,7 +40,7 @@ cdef class _wrefdict(dict):
@cython.final
@cython.internal
cdef class _thread_deleted:
cdef class _greenlet_deleted:
cdef object idt
cdef object wrdicts
......@@ -19,7 +50,7 @@ cdef class _thread_deleted:
cdef class _local_deleted:
cdef str key
cdef object wrthread
cdef _thread_deleted thread_deleted
cdef _greenlet_deleted greenlet_deleted
@cython.final
@cython.internal
......@@ -30,13 +61,36 @@ cdef class _localimpl:
cdef object __weakref__
@cython.final
@cython.internal
cdef class _localimpl_dict_entry:
cdef object wrgreenlet
cdef dict localdict
@cython.locals(localdict=dict, key=str, greenlet=greenlet,
greenlet_deleted=_greenlet_deleted,
local_deleted=_local_deleted)
cdef dict _localimpl_create_dict(_localimpl self)
@cython.locals(entry=_localimpl_dict_entry, greenlet=greenlet)
cdef inline dict _localimpl_get_dict(_localimpl self)
cdef set _local_attrs
cdef class local:
cdef _localimpl _local__impl
cdef set _local_type_get_descriptors
cdef set _local_type_set_or_del_descriptors
cdef set _local_type_del_descriptors
cdef set _local_type_set_descriptors
cdef set _local_type_vars
cdef type _local_type
@cython.locals(impl=_localimpl,dct=dict)
cdef inline dict _local_get_dict(local self)
@cython.locals(entry=_localimpl_dict_entry)
cdef _local__copy_dict_from(local self, _localimpl impl, dict duplicate)
cdef tuple _local_find_descriptors(local self)
This diff is collapsed.
......@@ -46,11 +46,21 @@ class Sentinel(object):
class MyLocal(local):
CLASS_PROP = 42
def __init__(self):
local.__init__(self)
self.sentinel = Sentinel()
created_sentinels.append(id(self.sentinel))
@property
def desc(self):
return self
class MyLocalSubclass(MyLocal):
pass
class WithGetattr(local):
def __getattr__(self, name):
......@@ -215,6 +225,22 @@ class GeventLocalTestCase(greentest.TestCase):
self.assertNotEqual(a.path, b.path, 'The values in the two objects must be different')
def test_class_attr(self, kind=MyLocal):
mylocal = kind()
self.assertEqual(42, mylocal.CLASS_PROP)
mylocal.CLASS_PROP = 1
self.assertEqual(1, mylocal.CLASS_PROP)
self.assertEqual(mylocal.__dict__['CLASS_PROP'], 1)
del mylocal.CLASS_PROP
self.assertEqual(42, mylocal.CLASS_PROP)
self.assertIs(mylocal, mylocal.desc)
def test_class_attr_subclass(self):
self.test_class_attr(kind=MyLocalSubclass)
def test_locals_collected_when_greenlet_dead_but_still_referenced(self):
# https://github.com/gevent/gevent/issues/387
import gevent
......
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