Skip to content

Instantly share code, notes, and snippets.

@foxwhite25
Created May 2, 2022 13:55
Show Gist options
  • Save foxwhite25/4bc10c4ee741a9ba16096cd005cabdc9 to your computer and use it in GitHub Desktop.
Save foxwhite25/4bc10c4ee741a9ba16096cd005cabdc9 to your computer and use it in GitHub Desktop.
from __future__ import annotations
import json
import textwrap
import typing
from timeit import timeit
import attr
import sys
class Code(typing.Protocol):
name: str
define: str
create: str
getattr: str | None
setattr: str | None
supports_mutable: bool | str
supports_immutable: bool | str
supports_slots: bool | str
supports_kw_getset: bool | str
supports_converters: bool | str
supports_validators: bool | str
typesafe: bool | str
stdlib: bool | str
support_keys = [
'supports_mutable',
'supports_immutable',
'supports_slots',
'supports_defaults',
'supports_default_factory',
'supports_kw_getset',
'supports_converters',
'supports_validators',
'typesafe',
'stdlib',
]
class TupleCode:
name = 'tuple'
define = "n, f, s = 42, 4.5, 'hello'"
create = "x = n, f, s"
getattr = "y = x[0]"
setattr = None
supports_mutable = False
supports_immutable = True
supports_slots = True
supports_defaults = False
supports_default_factory = False
supports_kw_getset = False
supports_converters = False
supports_validators = False
typesafe = False
stdlib = True
class CollectionsNamedTupleCode:
name = 'namedtuple'
define = textwrap.dedent("""
from collections import namedtuple
T = namedtuple('T', ['n', 'f', 's'])
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x[0]"
setattr = None
supports_mutable = False
supports_immutable = True
supports_slots = True
supports_defaults = True
supports_default_factory = False
supports_kw_getset = True
supports_converters = False
supports_validators = False
typesafe = False
stdlib = True
class TypingNamedTupleCode:
name = 'NamedTuple'
define = textwrap.dedent("""
from typing import NamedTuple
class T(NamedTuple):
n: int
f: float
s: str
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x[0]"
setattr = None
supports_mutable = False
supports_immutable = True
supports_slots = True
supports_defaults = True
supports_default_factory = False
supports_kw_getset = True
supports_converters = False
supports_validators = False
typesafe = True
stdlib = True
class DataClassCode:
name = 'dataclass'
define = textwrap.dedent("""
from dataclasses import dataclass
@dataclass
class T:
n: int
f: float
s: str
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = True
supports_defaults = True
supports_slots = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = False
supports_validators = False
typesafe = True
stdlib = True
class DictCode:
name = 'dict'
define = ''
create = "x = {'n': 42, 'f': 4.5, 's': 'hello'}"
getattr = "y = x['n']"
setattr = "x['n'] = 0"
supports_mutable = True
supports_immutable = False
supports_slots = False
supports_defaults = False
supports_default_factory = False
supports_kw_getset = "by str"
supports_converters = False
supports_validators = False
typesafe = False
stdlib = True
class SimpleNameSpaceCode:
name = 'SimpleNamespace'
define = textwrap.dedent("""
from types import SimpleNamespace
""")
create = "x = SimpleNamespace(n=42, f=4.5, s='hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = False
supports_slots = False
supports_defaults = False
supports_default_factory = False
supports_kw_getset = True
supports_converters = False
supports_validators = False
typesafe = False
stdlib = True
class PydanticBaseModelCode:
name = 'pydantic'
define = textwrap.dedent("""
from pydantic import BaseModel
class T(BaseModel):
n: int
f: float
s: str
""")
create = "x = T(n=42, f=4.5, s='hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = True
supports_slots = True
supports_defaults = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = True
supports_validators = True
typesafe = True
stdlib = False
class PlainClassCode:
name = 'plain class'
define = textwrap.dedent("""
class T:
def __init__(self, n: int, f: float, s: str):
self.n = n
self.f = f
self.s = s
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = "manual"
supports_slots = True
supports_defaults = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = "manual"
supports_validators = "manual"
typesafe = True
stdlib = True
class PlainClassSlotsCode:
name = 'plain class (slots)'
define = textwrap.dedent("""
class T:
__slots__ = 'n', 'f', 's'
def __init__(self, n, f, s):
self.n = n
self.f = f
self.s = s
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = "manual"
supports_slots = True
supports_defaults = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = "manual"
supports_validators = "manual"
typesafe = True
stdlib = True
class AttrClassCode:
name = 'attr class'
define = textwrap.dedent("""
import attr
@attr.s
class T:
n = attr.ib()
f = attr.ib()
s = attr.ib()
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = True
supports_slots = True
supports_defaults = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = True
supports_validators = True
typesafe = True
stdlib = False
class AttrClassSlotsCode:
name = 'attr class (slots)'
define = textwrap.dedent("""
import attr
@attr.s(slots=True)
class T:
n = attr.ib()
f = attr.ib()
s = attr.ib()
""")
create = "x = T(42, 4.5, 'hello')"
getattr = "y = x.n"
setattr = "x.n = 0"
supports_mutable = True
supports_immutable = True
supports_slots = True
supports_defaults = True
supports_default_factory = True
supports_kw_getset = True
supports_converters = True
supports_validators = True
typesafe = True
stdlib = False
code_classes = [
TupleCode,
TypingNamedTupleCode,
CollectionsNamedTupleCode,
DataClassCode,
DictCode,
SimpleNameSpaceCode,
PydanticBaseModelCode,
PlainClassCode,
PlainClassSlotsCode,
AttrClassCode,
AttrClassSlotsCode,
]
def run_timeit(name, stmt, setup, trials=1_000_000):
total_time = timeit(stmt=stmt, setup=setup, number=trials, globals=globals())
avg_time_s = total_time / trials
avg_time_ns = avg_time_s * 1_000_000_000
return name, avg_time_ns
def run_create(code: Code, trials=1_000_000) -> tuple[str, float]:
setup = code.define
stmt = code.create
return run_timeit(code.name, stmt, setup, trials)
def run_getattr(code: Code, trials=1_000_000) -> tuple[str, float]:
setup = code.define + '\n' + code.create
stmt = code.getattr
return run_timeit(code.name, stmt, setup, trials)
def run_setattr(code: Code, trials=1_000_000) -> tuple[str, float]:
setup = code.define + '\n' + code.create
stmt = code.setattr
if stmt is None:
return code.name, float('inf')
return run_timeit(code.name, stmt, setup, trials)
import sys
from numbers import Number
from collections import deque
from collections.abc import Set, Mapping
from pympler.asizeof import asizeof as getsize # pip install Pympler
# Note: getsize function was replaced for licence compatibility reasons
# so you may find slightly different results than seen in the video
def run_sizeof(code: Code) -> tuple[str, int]:
setup = code.define + '\n' + code.create + '\n'
exec(setup)
size = getsize(locals()['x'])
return code.name, size
def run_all_tests_for_function(f, title, fmt):
print(title)
cases: list[tuple[str, float]] = []
for cls in code_classes:
cases.append(f(cls))
cases.sort(key=lambda x: x[1])
for i, (name, t) in enumerate(cases):
print(fmt.format(i, name, t))
print()
return cases
def test_creation_speeds():
return run_all_tests_for_function(run_create, "creation speeds", '{}: {} - {:.0f} ns')
def test_getattr_speeds():
return run_all_tests_for_function(run_getattr, "getattr speeds", '{}: {} - {:.0f} ns')
def test_setattr_speeds():
return run_all_tests_for_function(run_setattr, "setattr speeds", '{}: {} - {:.0f} ns')
def test_mem_usage():
return run_all_tests_for_function(run_sizeof, "mem usage", '{}: {} - {} bytes')
def test_key(key):
return [(code.name, getattr(code, key)) for code in code_classes]
def main():
create_cases = test_creation_speeds()
getattr_cases = test_getattr_speeds()
setattr_cases = test_setattr_speeds()
mem_cases = test_mem_usage()
data = {
'create': create_cases,
'getattr': getattr_cases,
'setattr': setattr_cases,
'mem': mem_cases
}
for key in support_keys:
data[key] = test_key(key)
with open('results.out', 'w', encoding='utf-8') as f:
json.dump(data, f)
if __name__ == '__main__':
main()
@foxwhite25
Copy link
Author

我电脑的结果

creation speeds
0: tuple - 27 ns
1: dict - 63 ns
2: SimpleNamespace - 143 ns
3: plain class (slots) - 153 ns
4: attr class (slots) - 165 ns
5: namedtuple - 179 ns
6: plain class - 185 ns
7: dataclass - 188 ns
8: attr class - 199 ns
9: NamedTuple - 200 ns
10: pydantic - 4184 ns

getattr speeds
0: tuple - 16 ns
1: namedtuple - 16 ns
2: NamedTuple - 17 ns
3: plain class (slots) - 17 ns
4: attr class - 18 ns
5: dataclass - 18 ns
6: attr class (slots) - 19 ns
7: plain class - 19 ns
8: dict - 19 ns
9: pydantic - 19 ns
10: SimpleNamespace - 22 ns

setattr speeds
0: plain class (slots) - 21 ns
1: dict - 22 ns
2: attr class (slots) - 24 ns
3: attr class - 24 ns
4: dataclass - 26 ns
5: SimpleNamespace - 27 ns
6: plain class - 27 ns
7: pydantic - 369 ns
8: tuple - inf ns
9: NamedTuple - inf ns
10: namedtuple - inf ns

mem usage
0: plain class (slots) - 168 bytes
1: tuple - 176 bytes
2: NamedTuple - 176 bytes
3: namedtuple - 176 bytes
4: attr class (slots) - 176 bytes
5: dataclass - 432 bytes
6: plain class - 432 bytes
7: attr class - 432 bytes
8: dict - 512 bytes
9: SimpleNamespace - 552 bytes
10: pydantic - 560 bytes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment