Skip to content

Commit

Permalink
Merge branch 'faster-eq-hash-comparison'
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Mar 19, 2020
2 parents 4c4e1c9 + 04152ae commit 6c98b33
Show file tree
Hide file tree
Showing 17 changed files with 1,169 additions and 292 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,5 +1,6 @@
*.egg-info
*.pyc
*.pyo
*.so
__pycache__
.coverage
Expand Down
11 changes: 11 additions & 0 deletions CHANGES.rst
Expand Up @@ -210,6 +210,17 @@
hierarchy, ``Interface`` could be assigned too high a priority.
See `issue 8 <https://github.com/zopefoundation/zope.interface/issues/8>`_.

- Implement sorting, equality, and hashing in C for ``Interface``
objects. In micro benchmarks, this makes those operations 40% to 80%
faster. This translates to a 20% speed up in querying adapters.

Note that this changes certain implementation details. In
particular, ``InterfaceClass`` now has a non-default metaclass, and
it is enforced that ``__module__`` in instances of
``InterfaceClass`` is read-only.

See `PR 183 <https://github.com/zopefoundation/zope.interface/pull/183>`_.


4.7.2 (2020-03-10)
==================
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Expand Up @@ -18,3 +18,4 @@ global-exclude coverage.xml
global-exclude appveyor.yml

prune docs/_build
prune benchmarks
1 change: 1 addition & 0 deletions benchmarks/.gitignore
@@ -0,0 +1 @@
*.json
234 changes: 234 additions & 0 deletions benchmarks/micro.py
@@ -0,0 +1,234 @@
import pyperf

from zope.interface import Interface
from zope.interface import classImplements
from zope.interface import implementedBy
from zope.interface.interface import InterfaceClass
from zope.interface.registry import Components

# Long, mostly similar names are a worst case for equality
# comparisons.
ifaces = [
InterfaceClass('I' + ('0' * 20) + str(i), (Interface,), {})
for i in range(100)
]

class IWideInheritance(*ifaces):
"""
Inherits from 100 unrelated interfaces.
"""

class WideInheritance(object):
pass
classImplements(WideInheritance, IWideInheritance)

def make_deep_inheritance():
children = []
base = Interface
for iface in ifaces:
child = InterfaceClass('IDerived' + base.__name__, (iface, base,), {})
base = child
children.append(child)
return children

deep_ifaces = make_deep_inheritance()

class DeepestInheritance(object):
pass
classImplements(DeepestInheritance, deep_ifaces[-1])

def make_implementer(iface):
c = type('Implementer' + iface.__name__, (object,), {})
classImplements(c, iface)
return c

implementers = [
make_implementer(iface)
for iface in ifaces
]

providers = [
implementer()
for implementer in implementers
]

INNER = 100

def bench_in(loops, o):
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
o.__contains__(Interface)

return pyperf.perf_counter() - t0

def bench_sort(loops, objs):
import random
rand = random.Random(8675309)

shuffled = list(objs)
rand.shuffle(shuffled)

t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
sorted(shuffled)

return pyperf.perf_counter() - t0

def bench_query_adapter(loops, components, objs=providers):
# One time through to prime the caches
for iface in ifaces:
for provider in providers:
components.queryAdapter(provider, iface)

t0 = pyperf.perf_counter()
for _ in range(loops):
for iface in ifaces:
for provider in objs:
components.queryAdapter(provider, iface)
return pyperf.perf_counter() - t0


def bench_getattr(loops, name, get=getattr):
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
get(Interface, name) # 1
get(Interface, name) # 2
get(Interface, name) # 3
get(Interface, name) # 4
get(Interface, name) # 5
get(Interface, name) # 6
get(Interface, name) # 7
get(Interface, name) # 8
get(Interface, name) # 9
get(Interface, name) # 10
return pyperf.perf_counter() - t0

runner = pyperf.Runner()

runner.bench_time_func(
'read __module__', # stored in C, accessed through __getattribute__
bench_getattr,
'__module__',
inner_loops=INNER * 10
)

runner.bench_time_func(
'read __name__', # stored in C, accessed through PyMemberDef
bench_getattr,
'__name__',
inner_loops=INNER * 10
)

runner.bench_time_func(
'read __doc__', # stored in Python instance dictionary directly
bench_getattr,
'__doc__',
inner_loops=INNER * 10
)

runner.bench_time_func(
'read providedBy', # from the class, wrapped into a method object.
bench_getattr,
'providedBy',
inner_loops=INNER * 10
)

runner.bench_time_func(
'query adapter (no registrations)',
bench_query_adapter,
Components(),
inner_loops=1
)

def populate_components():
def factory(o):
return 42

pop_components = Components()
for iface in ifaces:
for other_iface in ifaces:
pop_components.registerAdapter(factory, (iface,), other_iface, event=False)

return pop_components

runner.bench_time_func(
'query adapter (all trivial registrations)',
bench_query_adapter,
populate_components(),
inner_loops=1
)

runner.bench_time_func(
'query adapter (all trivial registrations, wide inheritance)',
bench_query_adapter,
populate_components(),
[WideInheritance()],
inner_loops=1
)

runner.bench_time_func(
'query adapter (all trivial registrations, deep inheritance)',
bench_query_adapter,
populate_components(),
[DeepestInheritance()],
inner_loops=1
)

runner.bench_time_func(
'sort interfaces',
bench_sort,
ifaces,
inner_loops=INNER,
)

runner.bench_time_func(
'sort implementedBy',
bench_sort,
[implementedBy(p) for p in implementers],
inner_loops=INNER,
)

runner.bench_time_func(
'sort mixed',
bench_sort,
[implementedBy(p) for p in implementers] + ifaces,
inner_loops=INNER,
)

runner.bench_time_func(
'contains (empty dict)',
bench_in,
{},
inner_loops=INNER
)

runner.bench_time_func(
'contains (populated dict: interfaces)',
bench_in,
{k: k for k in ifaces},
inner_loops=INNER
)

runner.bench_time_func(
'contains (populated list: interfaces)',
bench_in,
ifaces,
inner_loops=INNER
)

runner.bench_time_func(
'contains (populated dict: implementedBy)',
bench_in,
{implementedBy(p): 1 for p in implementers},
inner_loops=INNER
)

runner.bench_time_func(
'contains (populated list: implementedBy)',
bench_in,
[implementedBy(p) for p in implementers],
inner_loops=INNER
)
1 change: 1 addition & 0 deletions src/zope/interface/_compat.py
Expand Up @@ -166,4 +166,5 @@ def find_impl():
# name (for testing and documentation)
globs[name + 'Py'] = py_impl
globs[name + 'Fallback'] = py_impl

return c_impl

0 comments on commit 6c98b33

Please sign in to comment.