Created
January 23, 2023 17:14
-
-
Save jcrist/9bfe44f60533225d5f8383791f2fe734 to your computer and use it in GitHub Desktop.
A benchmark comparing init performance of various dataclass-like libraries
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
| """A quick benchmark comparing how quickly `__init__` with default values runs | |
| for various dataclass-like libraries. | |
| We also compare against the time it takes to initialize a `dict` or `tuple` | |
| with the same data, as a "low-bar" for pure-python implementations. | |
| """ | |
| import timeit | |
| import attrs | |
| import dataclasses | |
| import msgspec | |
| import pydantic | |
| @attrs.define | |
| class BenchAttrs: | |
| a: int = 0 | |
| b: str = "" | |
| c: list[int] = attrs.field(factory=list) | |
| d: set[int] = attrs.field(factory=set) | |
| e: dict[str, str] = attrs.field(factory=dict) | |
| @dataclasses.dataclass | |
| class BenchDataclass: | |
| a: int = 0 | |
| b: str = "" | |
| c: list[int] = dataclasses.field(default_factory=list) | |
| d: set[int] = dataclasses.field(default_factory=set) | |
| e: dict[str, str] = dataclasses.field(default_factory=dict) | |
| class BenchMsgspec(msgspec.Struct): | |
| a: int = 0 | |
| b: str = "" | |
| c: list[int] = msgspec.field(default_factory=list) | |
| d: set[int] = msgspec.field(default_factory=set) | |
| e: dict[str, str] = msgspec.field(default_factory=dict) | |
| class BenchPydantic(pydantic.BaseModel): | |
| a: int = 0 | |
| b: str = "" | |
| c: list[int] = pydantic.Field(default_factory=list) | |
| d: set[int] = pydantic.Field(default_factory=set) | |
| e: dict[str, str] = pydantic.Field(default_factory=dict) | |
| def bench_dict(): | |
| return {"a": 0, "b": "", "c": [], "d": set(), "e": {}} | |
| def bench_tuple(): | |
| return (0, "", [], set(), {}) | |
| def bench(cls): | |
| timer = timeit.Timer("cls()", globals={"cls": cls}) | |
| n, t = timer.autorange() | |
| return t / n | |
| def main(): | |
| results = sorted( | |
| [ | |
| ("attrs", bench(BenchAttrs)), | |
| ("dataclasses", bench(BenchDataclass)), | |
| ("msgspec", bench(BenchMsgspec)), | |
| ("pydantic", bench(BenchPydantic)), | |
| ("dict", bench(bench_dict)), | |
| ("tuple", bench(bench_tuple)), | |
| ], | |
| key=lambda r: r[1] | |
| ) | |
| max_name_length = max(len(n) for n, _ in results) | |
| fastest = results[0][1] | |
| for i, (name, t) in enumerate(results): | |
| # Format time output | |
| if t < 1e-6: | |
| time = f"{t * 1e9:.1f} ns" | |
| elif t < 1e-3: | |
| time = f"{t * 1e6:.1f} μs" | |
| else: | |
| time = f"{t * 1e3:.1f} ms" | |
| padding = " " * (max_name_length - len(name)) | |
| if i == 0: | |
| print(f"{name}:{padding} {time}") | |
| else: | |
| print(f"{name}:{padding} {time} ({t / fastest:.1f}x slower)") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this! My results (Python 3.13.9)