-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: optimize reject_recursive_repeats #3668
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
Conversation
thread_id = threading.get_ident() | ||
thread_local_args = (thread_id,) + arg_instances | ||
if thread_local_args in to_wrap.__already_called: # type: ignore | ||
thread_local_args = (threading.get_ident(), *map(id, args)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-2 var assignments, -2 var reads, -2 tuple creations per call
raise Web3ValueError(f"Recursively called {to_wrap} with {args!r}") | ||
to_wrap.__already_called[thread_local_args] = True # type: ignore | ||
add_call(thread_local_args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 attr lookup, -1 setitem per call
finally: | ||
del to_wrap.__already_called[thread_local_args] # type: ignore | ||
return wrapped_val | ||
remove_call(thread_local_args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 attr lookup per call
@@ -20,21 +22,22 @@ def reject_recursive_repeats(to_wrap: Callable[..., Any]) -> Callable[..., Any]: | |||
Prevent simple cycles by returning None when called recursively with same instance | |||
""" | |||
# types ignored b/c dynamically set attribute | |||
to_wrap.__already_called = {} # type: ignore | |||
already_called: Set[Tuple[int, ...]] = set() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 less attr lookup per access
try: | ||
wrapped_val = to_wrap(*args) | ||
return to_wrap(*args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 set var, -1 var lookup per call
Thanks for the PR! IMHO, the readability is about the same, and I'm not seeing any gains in test run times. If you can provide some benchmarks that prove that this helps, we'd be happy to take another look and merge it in. |
@kclowes I found a few minutes while waiting for some scripts to startup, and put together a benchmark for this: import functools
import threading
from time import time
from typing import Any, Callable, Set, Tuple
def reject_recursive_repeats_old(to_wrap: Callable[..., Any]) -> Callable[..., Any]:
"""
Prevent simple cycles by returning None when called recursively with same instance
"""
# types ignored b/c dynamically set attribute
to_wrap.__already_called = {} # type: ignore
@functools.wraps(to_wrap)
def wrapped(*args: Any) -> Any:
arg_instances = tuple(map(id, args))
thread_id = threading.get_ident()
thread_local_args = (thread_id,) + arg_instances
if thread_local_args in to_wrap.__already_called: # type: ignore
raise Exception(f"Recursively called {to_wrap} with {args!r}")
to_wrap.__already_called[thread_local_args] = True # type: ignore
try:
wrapped_val = to_wrap(*args)
finally:
del to_wrap.__already_called[thread_local_args] # type: ignore
return wrapped_val
return wrapped
def reject_recursive_repeats_new(to_wrap: Callable[..., Any]) -> Callable[..., Any]:
"""
Prevent simple cycles by returning None when called recursively with same instance
"""
# types ignored b/c dynamically set attribute
already_called: Set[Tuple[int, ...]] = set()
to_wrap.__already_called = already_called # type: ignore
add_call = already_called.add
remove_call = already_called.remove
@functools.wraps(to_wrap)
def wrapped(*args: Any) -> Any:
thread_local_args = (threading.get_ident(), *map(id, args))
if thread_local_args in already_called:
raise Exception(f"Recursively called {to_wrap} with {args!r}")
add_call(thread_local_args)
try:
return to_wrap(*args)
finally:
remove_call(thread_local_args)
return wrapped
@reject_recursive_repeats_old
def test_old():
pass
@reject_recursive_repeats_new
def test_new():
pass
start = time()
for _ in range(10_000):
test_old()
print(f"old: {time() - start}")
start = time()
for _ in range(10_000):
test_new()
print(f"new: {time() - start}")
The new implementation takes 25% less time! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sick, thanks for putting in the work for the benchmarking script!
What was wrong?
Nothing was wrong, but reject_recursive_repeats was ripe for optimization since it is used frequently throughout the codebase
Related to Issue #N/A
Closes #N/A
How was it fixed?
I optimized the decorator by refactoring out unnecessary variables and repetitive attr lookups
Todo:
Cute Animal Picture