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
Last active
December 21, 2019 05:46
-
-
Save Bobronium/94b5ca3e4098669d93a6127e70359307 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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