Last active
April 10, 2025 09:15
-
-
Save wolph/02fae0b20b914354734aaac01c06d23b to your computer and use it in GitHub Desktop.
Benchmark namedtuple vs dataclass vs dict
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 sys | |
import enum | |
import math | |
import random | |
import timeit | |
import typing | |
import dataclasses | |
import collections | |
repeat = 5 | |
number = 1000 | |
N = 5000 | |
class PointTuple(typing.NamedTuple): | |
x: int | |
y: int | |
z: int | |
@dataclasses.dataclass | |
class PointDataclass: | |
x: int | |
y: int | |
z: int | |
@dataclasses.dataclass(slots=True) | |
class PointDataclassSlots: | |
x: int | |
y: int | |
z: int | |
class PointObject: | |
__slots__ = 'x', 'y', 'z' | |
x: int | |
y: int | |
z: int | |
def test_namedtuple_attr(): | |
point = PointTuple(1234, 5678, 9012) | |
for i in range(N): | |
x, y, z = point.x, point.y, point.z | |
def test_namedtuple_index(): | |
point = PointTuple(1234, 5678, 9012) | |
for i in range(N): | |
x, y, z = point | |
def test_namedtuple_unpack(): | |
point = PointTuple(1234, 5678, 9012) | |
for i in range(N): | |
x, *y = point | |
def test_dataclass(): | |
point = PointDataclass(1234, 5678, 9012) | |
for i in range(N): | |
x, y, z = point.x, point.y, point.z | |
def test_dataclass_slots(): | |
point = PointDataclassSlots(1234, 5678, 9012) | |
for i in range(N): | |
x, y, z = point.x, point.y, point.z | |
def test_dict(): | |
point = dict(x=1234, y=5678, z=9012) | |
for i in range(N): | |
x, y, z = point['x'], point['y'], point['z'] | |
def test_slots(): | |
point = PointObject() | |
point.x = 1234 | |
point.y = 5678 | |
point.z = 9012 | |
for i in range(N): | |
x, y, z = point.x, point.y, point.z | |
class PointEnum(enum.Enum): | |
x = 1 | |
y = 2 | |
z = 3 | |
def test_enum_attr(): | |
point = PointEnum | |
for i in range(N): | |
x, y, z = point.x, point.y, point.z | |
def test_enum_call(): | |
point = PointEnum | |
for i in range(N): | |
x, y, z = point(1), point(2), point(3) | |
def test_enum_item(): | |
point = PointEnum | |
for i in range(N): | |
x, y, z = point['x'], point['y'], point['z'] | |
if __name__ == '__main__': | |
tests = [ | |
test_namedtuple_attr, | |
test_namedtuple_index, | |
test_namedtuple_unpack, | |
test_dataclass, | |
test_dataclass_slots, | |
test_dict, | |
test_slots, | |
test_enum_attr, | |
test_enum_call, | |
test_enum_item, | |
] | |
print(f'Running tests {repeat} times with {number} calls.') | |
print(f'Using {N} iterations in the loop') | |
results = collections.defaultdict(lambda: math.inf) | |
for i in range(repeat): | |
# Shuffling tests to prevent skewed results due to CPU boosting or | |
# thermal throttling | |
random.shuffle(tests) | |
print(f'Run {i}:', end=' ') | |
for t in tests: | |
name = t.__name__ | |
print(name, end=', ') | |
sys.stdout.flush() | |
timer = timeit.Timer(f'{name}()', f'from __main__ import {name}') | |
results[name] = min(results[name], timer.timeit(number)) | |
print() | |
for name, result in sorted(results.items(), key=lambda x: x[::-1]): | |
print(f'{name:30} {result:.3f}s') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've also added a dataclass with slots with this version