Skip to content

Instantly share code, notes, and snippets.

@wolph
Last active May 10, 2024 13:02
Show Gist options
  • Save wolph/02fae0b20b914354734aaac01c06d23b to your computer and use it in GitHub Desktop.
Save wolph/02fae0b20b914354734aaac01c06d23b to your computer and use it in GitHub Desktop.
Benchmark namedtuple vs dataclass vs dict
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')
@wolph
Copy link
Author

wolph commented Jan 7, 2023

Also, I don't think "test_namedtuple_unpack" needs to be included because that is more on the code that handles the *args.

While I agree, it's bound to be a question/comment if I don't ;)
And sometimes these results can be surprising so it might make a difference somehow

I personally wanted to see how enums faired, so i added them. Here is the code if you want to include it. side note: they did not fair well.

Very cool! I'll rerun the tests on Python 3.11 and update the benchmark and the answer :)

@wolph
Copy link
Author

wolph commented Jan 7, 2023

I've also added a dataclass with slots with this version

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