Skip to content

Instantly share code, notes, and snippets.

@Bobronium
Last active December 21, 2019 05:46
Show Gist options
  • Save Bobronium/94b5ca3e4098669d93a6127e70359307 to your computer and use it in GitHub Desktop.
Save Bobronium/94b5ca3e4098669d93a6127e70359307 to your computer and use it in GitHub Desktop.
ATTR_ACCESS:
Testing with 10000 repeats, result is average of 10 tests:

    >>> NewEnum.foo  # 0.6069349023270748 ms
    <NewEnum.foo: 1>

    >>> OldEnum.foo  # 4.5089948174334005 ms
    <OldEnum.foo: 1>

    >>> NewEnum.foo.value  # 0.965516420197984 ms
    1

    >>> OldEnum.foo.value  # 5.234090615261106 ms
    1

    >>> NewEnum.value.value  # 0.6496817059283957 ms
    3

    >>> OldEnum.value.value  # 19.718928336346387 ms
    3

    >>> try:     NewEnum.value.value = 'new' except: pass  # 4.842046525117963 ms
    AttributeError("Can't set attribute")

    >>> try:     OldEnum.value.value = 'new' except: pass  # 24.484108522222897 ms
    AttributeError("can't set attribute")


NewEnum: total: 7.0641796 ms, average: 1.1652198 ms (Fastest)
OldEnum: total: 53.9461223 ms, average: 10.3317085 ms, ~ x8.87 times slower than NewEnum 


TRYING_VALUES:
Testing with 10000 repeats, result is average of 10 tests:

    >>> try:     NewEnum(42) except: pass  # 78.72758108843522 ms
    ValueError('42 is not a valid NewEnum')

    >>> try:     OldEnum(42) except: pass  # 114.41087651609284 ms
    ValueError('42 is not a valid OldEnum')

    >>> NewEnum(1)  # 5.5310626853523 ms
    <NewEnum.foo: 1>

    >>> OldEnum(1)  # 11.950902804706907 ms
    <OldEnum.foo: 1>

    >>> NewEnum(NewEnum.foo)  # 7.8239688754355665 ms
    <NewEnum.foo: 1>

    >>> OldEnum(OldEnum.foo)  # 14.407295876159042 ms
    <OldEnum.foo: 1>


NewEnum: total: 92.0826126 ms, average: 15.0471483 ms (Fastest)
OldEnum: total: 140.7690752 ms, average: 27.0074450 ms, ~ x1.79 times slower than NewEnum 


ITERATION:
Testing with 10000 repeats, result is average of 10 tests:

    >>> for member in NewEnum: pass  # 4.772927918118728 ms
    

    >>> for member in OldEnum: pass  # 18.128567396481568 ms
    

    >>> for member in reversed(NewEnum): pass  # 5.989089971146384 ms
    

    >>> for member in reversed(OldEnum): pass  # 24.663105321350926 ms
    


NewEnum: total: 10.7620179 ms, average: 5.3465404 ms (Fastest)
OldEnum: total: 42.7916727 ms, average: 21.1448993 ms, ~ x3.95 times slower than NewEnum 


FLAG_SPECIFIC:
Testing with 10000 repeats, result is average of 10 tests:

    >>> NewEnum.foo ^ NewEnum.value  # 9.087847910741713 ms
    <NewEnum.bar: 2>

    >>> OldEnum.foo ^ OldEnum.value  # 32.015764784468644 ms
    <OldEnum.bar: 2>

    >>> NewEnum.foo & NewEnum.value  # 8.727216610750942 ms
    <NewEnum.foo: 1>

    >>> OldEnum.foo & OldEnum.value  # 27.78424711260569 ms
    <OldEnum.foo: 1>

    >>> NewEnum.foo | NewEnum.value  # 8.358745643769 ms
    <NewEnum.value: 3>

    >>> OldEnum.foo | OldEnum.value  # 35.129276640801834 ms
    <OldEnum.value: 3>

    >>> ~NewEnum.value  # 50.95385726892975 ms
    <NewEnum.0: 0>

    >>> ~OldEnum.value  # 111.6387554416224 ms
    <OldEnum.0: 0>


NewEnum: total: 77.1276674 ms, average: 13.5570047 ms (Fastest)
OldEnum: total: 206.5680440 ms, average: 43.2177026 ms, ~ x3.19 times slower than NewEnum 


TOTAL TIME:

NewEnum: total: 187.0364775 ms, average: 5.6561728 ms (Fastest)
OldEnum: total: 444.0749142 ms, average: 22.3642129 ms, ~ x3.95 times slower than NewEnum 
import enum
import math
import time
from timeit import timeit
from typing import Iterable, Dict, List, Tuple, Union, TypeVar
from Lib import enum as new_enum
TRY_EXCEPT_BLOCK_TMPL = 'try:\n {expr}\nexcept: pass'
def geomean(numbers) -> float:
return math.exp(math.fsum(math.log(x) for x in numbers) / len(numbers))
def calculate_difference(time_elapsed: Dict[type, List[float]]) -> str:
time_elapsed = time_elapsed.copy()
fastest = min(time_elapsed, key=lambda i: sum(time_elapsed[i]))
fastest_time = time_elapsed.pop(fastest)
average_fastest = geomean(fastest_time)
fastest_total = sum(fastest_time)
result = (
f'\n{fastest.__name__:<7}: total: {fastest_total:.7f} ms, average: {average_fastest:.7f} ms (Fastest)'
)
for type_, elapsed in time_elapsed.items():
average = geomean(elapsed)
total = sum(elapsed)
result += (
f'\n{type_.__name__:<7}: total: {total:.7f} ms, average: {average:.7f} ms, '
f'~ x{average / average_fastest:.2f} times slower than {fastest.__name__} '
)
return result
def eval_and_timeit(code, global_ns, number, repeats=3, setup='pass', **local_ns):
result = None
exception = None
try:
result = eval(code, global_ns, local_ns)
except SyntaxError:
try:
exec(code, global_ns, local_ns)
except SyntaxError:
raise
except Exception as e:
exception = e
except Exception as e:
exception = e
if exception is not None:
code = TRY_EXCEPT_BLOCK_TMPL.format(expr=code)
result = exception
return code, result, geomean([timeit(code, setup, globals=global_ns, number=number) for _ in range(repeats)])
T = TypeVar('T')
def test(
*objects: T,
expressions: Iterable[Union[str, Tuple[str, str]]],
number: int = 10000,
repeats: int = 10,
group_by_objects: bool = False,
format_mapping: Dict[str, str] = None,
pause_interval=0,
**globals_ns
) -> Dict[T, List[float]]:
"""
:param objects: objects to test1
:param expressions: expressions to test1 on objects
:param number: number of repeats for each expression (passed in timeit)
:param repeats: number of repeats for timeit
:param group_by_objects: if True, expressions will be evaluated and tested in order of objects, else of expressions
:param format_mapping: mapping to format str where keys is keys in str and values is attrs of current object
:param pause_interval: interval between tests
:param globals_ns: namespace for test1
:return: dict with objects as keys and total time elapsed by them as values
>>> class Foo:
BAR = 'baz'
>>> class Bar:
@property
def BAR(self):
return 'baz'
>>> test(Foo, Bar, expressions=('{obj}().BAR', '{obj}().BAR = 1}'), Foo=Foo, Bar=Bar)
Testing with 1000000 repeats:
>>> Foo().BAR # 0.1405952029999753 ms
'baz'
>>> Bar().BAR # 0.27527517399721546 ms
'baz'
>>> Foo().BAR = 1 # 0.20322119499905966 ms
>>> try: Bar().BAR = 1 except: pass # 0.41546584199750214 ms
AttributeError("can't set attribute",)
"""
print(f'Testing with {number} repeats, result is average of {repeats} tests:\n')
if group_by_objects:
obj_expressions = ((obj, expression) for obj in objects for expression in expressions)
else: # by expressions
obj_expressions = ((obj, expression) for expression in expressions for obj in objects)
if format_mapping is None:
format_mapping = {'obj': '__name__'} # expression.format(obj=obj.__name__)
time_elapsed = {}
for obj, expression in obj_expressions:
if not isinstance(expression, str):
expression, setup = expression
else:
setup = 'pass'
formatting = {key: getattr(obj, attr) for key, attr in format_mapping.items()}
code = expression.format(**formatting)
setup = setup.format(**formatting)
time.sleep(pause_interval)
code, result, elapsed = eval_and_timeit(code, setup=setup, number=number, repeats=repeats, global_ns=globals_ns)
elapsed *= 1000
time_elapsed.setdefault(obj, []).append(elapsed)
code = code.replace('\n', ' ')
result = repr(result) if result is not None else ''
print(f' >>> {code} # {elapsed} ms\n {result}\n')
return time_elapsed
class OldEnum(enum.Flag):
foo = 1
bar = 2
value = 3
class NewEnum(new_enum.Flag):
foo = 1
bar = 2
value = 3
if __name__ == '__main__':
CASES = dict(
ATTR_ACCESS=(
'{obj}.foo',
'{obj}.foo.value',
'{obj}.value.value',
"{obj}.value.value = 'new'",
),
TRYING_VALUES=(
"{obj}(42)",
"{obj}(1)",
'{obj}({obj}.foo)',
),
ITERATION=(
'for member in {obj}: pass',
'for member in reversed({obj}): pass',
),
FLAG_SPECIFIC=(
'{obj}.foo ^ {obj}.value',
'{obj}.foo & {obj}.value',
'{obj}.foo | {obj}.value',
'~{obj}.value',
),
)
candidates = (
NewEnum,
OldEnum,
)
total_time_elapsed = {}
for name, expr in CASES.items():
print(f'\n\n{name}:')
time_info = test(*candidates, expressions=expr, group_by_objects=False, **globals())
for t, elapsed_ in time_info.items():
total_time_elapsed.setdefault(t, []).extend(elapsed_)
print((calculate_difference(time_info)))
print(f'\n\nTOTAL TIME:')
print((calculate_difference(total_time_elapsed)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment