Commit a7b9b614 authored by Jason Madden's avatar Jason Madden

Working on making gevent.local faster.

So far, this makes accessing simple attributes about 3.5x faster on
Python 3.4 (I haven't repeated benchmarks for other platforms yet).

Refs #1020.
parent 13d860ae
...@@ -54,6 +54,9 @@ ...@@ -54,6 +54,9 @@
- Monkey-patching after the :mod:`ssl` module has been imported now - Monkey-patching after the :mod:`ssl` module has been imported now
prints a warning because this can produce ``RecursionError``. prints a warning because this can produce ``RecursionError``.
- :class:`gevent.local.local` objects are now about 3.4 times faster
reading simple attributes. See :issue:`1020`.
1.2.2 (2017-06-05) 1.2.2 (2017-06-05)
================== ==================
......
...@@ -141,7 +141,7 @@ affects what we see: ...@@ -141,7 +141,7 @@ affects what we see:
from copy import copy from copy import copy
from weakref import ref from weakref import ref
from contextlib import contextmanager from functools import partial
from gevent.hub import getcurrent from gevent.hub import getcurrent
from gevent._compat import PYPY from gevent._compat import PYPY
from gevent.lock import RLock from gevent.lock import RLock
...@@ -152,10 +152,12 @@ __all__ = ["local"] ...@@ -152,10 +152,12 @@ __all__ = ["local"]
class _wrefdict(dict): class _wrefdict(dict):
"""A dict that can be weak referenced""" """A dict that can be weak referenced"""
_osa = object.__setattr__
_oga = object.__getattribute__
class _localimpl(object): class _localimpl(object):
"""A class managing thread-local dicts""" """A class managing thread-local dicts"""
__slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__' __slots__ = ('key', 'dicts', 'localargs', 'locallock', '__weakref__', 'dict_setter')
def __init__(self): def __init__(self):
# The key used in the Thread objects' attribute dicts. # The key used in the Thread objects' attribute dicts.
...@@ -221,10 +223,10 @@ class _localimpl(object): ...@@ -221,10 +223,10 @@ class _localimpl(object):
return localdict return localdict
@contextmanager def _get_dict(self):
def _patch(self): impl = _oga(self, '_local__impl')
impl = object.__getattribute__(self, '_local__impl') orig_dct = _oga(self, '__dict__')
orig_dct = object.__getattribute__(self, '__dict__') lock = impl.locallock
try: try:
dct = impl.get_dict() dct = impl.get_dict()
except KeyError: except KeyError:
...@@ -232,13 +234,14 @@ def _patch(self): ...@@ -232,13 +234,14 @@ def _patch(self):
# however, subclassed __init__ might switch, so we do need to acquire the lock here # however, subclassed __init__ might switch, so we do need to acquire the lock here
dct = impl.create_dict() dct = impl.create_dict()
args, kw = impl.localargs args, kw = impl.localargs
with impl.locallock: with lock:
self.__init__(*args, **kw) self.__init__(*args, **kw)
with impl.locallock:
object.__setattr__(self, '__dict__', dct)
yield
object.__setattr__(self, '__dict__', orig_dct)
setter = impl.dict_setter
return dct, orig_dct, lock, setter
_marker = object()
class local(object): class local(object):
""" """
...@@ -254,7 +257,8 @@ class local(object): ...@@ -254,7 +257,8 @@ class local(object):
impl = _localimpl() impl = _localimpl()
impl.localargs = (args, kw) impl.localargs = (args, kw)
impl.locallock = RLock() impl.locallock = RLock()
object.__setattr__(self, '_local__impl', impl) impl.dict_setter = partial(_osa, self, '__dict__')
_osa(self, '_local__impl', impl)
# We need to create the thread dict in anticipation of # We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it # __init__ being called, to make sure we don't call it
# again ourselves. # again ourselves.
...@@ -262,24 +266,69 @@ class local(object): ...@@ -262,24 +266,69 @@ class local(object):
return self return self
def __getattribute__(self, name): def __getattribute__(self, name):
with _patch(self): if name == '__class__':
return object.__getattribute__(self, name) return _oga(self, name)
dct, orig_dct, lock, setter = _get_dict(self)
if name == '__dict__':
return dct
# If there's no possible way we can switch, because this
# attribute is *not* found in the class where it might be a
# data descriptor (property), and it *is* in the dict
# then we don't need to swizzle the dict and take the lock.
# We don't have to worry about people overriding __getattribute__
# because if they did, the dict-swizzling would only last as
# long as we were in here anyway.
# Similarly, a __getattr__ will still be called by _oga() if needed
# if it's not in the dict.
if name in dct:
if not hasattr(type(self), name):
# We elide even setting the dict in the first place in
# this case.
return dct[name]
else:
# It's not in the dict at all. Is it in the type, but
# not a descriptor (has no __get__)? Then it can't execute any
# code and so doesn't need the lock or dct swizzle
type_attr = getattr(type(self), name, _marker)
if type_attr is not _marker and not hasattr(type_attr, '__get__'):
return type_attr
lock.acquire()
setter(dct)
try:
return _oga(self, name)
finally:
setter(orig_dct)
lock.release()
def __setattr__(self, name, value): def __setattr__(self, name, value):
if name == '__dict__': if name == '__dict__':
raise AttributeError( raise AttributeError(
"%r object attribute '__dict__' is read-only" "%r object attribute '__dict__' is read-only"
% self.__class__.__name__) % self.__class__.__name__)
with _patch(self): dct, orig_dct, lock, setter = _get_dict(self)
return object.__setattr__(self, name, value) lock.acquire()
setter(dct)
try:
return _osa(self, name, value)
finally:
setter(orig_dct)
lock.release()
def __delattr__(self, name): def __delattr__(self, name):
if name == '__dict__': if name == '__dict__':
raise AttributeError( raise AttributeError(
"%r object attribute '__dict__' is read-only" "%r object attribute '__dict__' is read-only"
% self.__class__.__name__) % self.__class__.__name__)
with _patch(self): dct, orig_dct, lock, setter = _get_dict(self)
lock.acquire()
setter(dct)
try:
return object.__delattr__(self, name) return object.__delattr__(self, name)
finally:
setter(orig_dct)
lock.release()
def __copy__(self): def __copy__(self):
impl = object.__getattribute__(self, '_local__impl') impl = object.__getattribute__(self, '_local__impl')
......
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