Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Interface hashing and comparison to C; 2.5 to 15x speedup in micro benchmarks #183

Merged
merged 12 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ global-exclude coverage.xml
global-exclude appveyor.yml

prune docs/_build
prune benchmarks
234 changes: 234 additions & 0 deletions benchmarks/micro.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading