Skip to content

Commit 7ec6b4d

Browse files
authored
Merge branch 'main' into fix-tvar-typing-generic-alias
2 parents 9641743 + d3b848c commit 7ec6b4d

4 files changed

Lines changed: 274 additions & 10 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,28 @@ support for [JSON](https://json.org), [MessagePack](https://msgpack.org),
2525

2626
- 🚀 **High performance encoders/decoders** for common protocols. The JSON and
2727
MessagePack implementations regularly
28-
[benchmark](https://msgspec.dev/benchmarks.html) as the fastest
28+
[benchmark](https://msgspec.dev/benchmarks) as the fastest
2929
options for Python.
3030

3131
- 🎉 **Support for a wide variety of Python types**. Additional types may be
3232
supported through
33-
[extensions](https://msgspec.dev/extending.html).
33+
[extensions](https://msgspec.dev/extending).
3434

3535
- 🔍 **Zero-cost schema validation** using familiar Python type annotations. In
36-
[benchmarks](https://msgspec.dev/benchmarks.html) `msgspec`
36+
[benchmarks](https://msgspec.dev/benchmarks) `msgspec`
3737
decodes *and* validates JSON faster than
3838
[orjson](https://github.com/ijl/orjson) can decode it alone.
3939

4040
- ✨ **A speedy Struct type** for representing structured data. If you already
4141
use [dataclasses](https://docs.python.org/3/library/dataclasses.html) or
4242
[attrs](https://www.attrs.org/en/stable/),
43-
[structs](https://msgspec.dev/structs.html) should feel familiar.
43+
[structs](https://msgspec.dev/structs) should feel familiar.
4444
However, they're
45-
[5-60x faster](https://msgspec.dev/benchmarks.html#structs)
45+
[5-60x faster](https://msgspec.dev/benchmarks#structs)
4646
for common operations.
4747

4848
All of this is included in a
49-
[lightweight library](https://msgspec.dev/benchmarks.html#library-size)
49+
[lightweight library](https://msgspec.dev/benchmarks#library-size)
5050
with no required dependencies.
5151

5252
---
@@ -97,10 +97,10 @@ msgspec.ValidationError: Expected `str`, got `int` - at `$.groups[0]`
9797
the nicities of validation libraries like
9898
[pydantic](https://pydantic.dev/docs/validation/latest/get-started/). For supported types,
9999
encoding/decoding a message with `msgspec` can be
100-
[~10-80x faster than alternative libraries](https://msgspec.dev/benchmarks.html).
100+
[~10-80x faster than alternative libraries](https://msgspec.dev/benchmarks).
101101

102102
<p align="center">
103-
<a href="https://msgspec.dev/benchmarks.html">
103+
<a href="https://msgspec.dev/benchmarks">
104104
<img src="https://raw.githubusercontent.com/msgspec/msgspec/main/docs/_static/bench-validation.svg">
105105
</a>
106106
</p>

‎tests/prof/perf/test_encode_decode.py‎

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import datetime
2+
import decimal
3+
import enum
4+
import random
5+
import uuid
6+
from typing import Literal
7+
18
import pytest
29
from pytest_codspeed import BenchmarkFixture
310

@@ -77,3 +84,227 @@ def func(data):
7784
res = benchmark(func, make_filesystem_data(1000))
7885

7986
assert isinstance(res, Directory)
87+
88+
89+
class SomeEnum(enum.Enum):
90+
FIELD_1 = enum.auto()
91+
FIELD_2 = enum.auto()
92+
FIELD_3 = enum.auto()
93+
FIELD_4 = enum.auto()
94+
FIELD_5 = enum.auto()
95+
FIELD_6 = enum.auto()
96+
FIELD_7 = enum.auto()
97+
FIELD_8 = enum.auto()
98+
FIELD_9 = enum.auto()
99+
FIELD_10 = enum.auto()
100+
101+
102+
@pytest.fixture(
103+
params=[
104+
pytest.param(msgspec.json, id="json"),
105+
pytest.param(msgspec.msgpack, id="msgpack"),
106+
]
107+
)
108+
def proto(request):
109+
return request.param
110+
111+
112+
sample_data_mark = pytest.mark.parametrize(
113+
"make_data,target_type",
114+
[
115+
pytest.param(
116+
lambda: random.sample(range(-100_000, 100_000), 10_000), list[int], id="int"
117+
),
118+
pytest.param(
119+
lambda: [random.uniform(-100_000.0, 100_000.0) for _ in range(10_000)],
120+
list[float],
121+
id="float",
122+
),
123+
pytest.param(
124+
lambda: [
125+
decimal.Decimal(random.uniform(-100_000.0, 100_000.0))
126+
for _ in range(10_000)
127+
],
128+
list[decimal.Decimal],
129+
id="decimal",
130+
),
131+
pytest.param(
132+
lambda: [
133+
datetime.date(2000, 1, 1) + datetime.timedelta(days=i)
134+
for i in range(1, 10_000)
135+
],
136+
list[datetime.date],
137+
id="date",
138+
),
139+
pytest.param(
140+
lambda: [
141+
datetime.datetime(2000, 1, 1) + datetime.timedelta(seconds=i)
142+
for i in range(1, 10_000)
143+
],
144+
list[datetime.datetime],
145+
id="datetime",
146+
),
147+
pytest.param(
148+
lambda: [
149+
datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC)
150+
+ datetime.timedelta(seconds=i)
151+
for i in range(1, 10_000)
152+
],
153+
list[datetime.datetime],
154+
id="datetimetz",
155+
),
156+
pytest.param(
157+
lambda: [datetime.timedelta(seconds=i) for i in range(1, 10_000)],
158+
list[datetime.timedelta],
159+
id="timedelta",
160+
),
161+
pytest.param(
162+
lambda: [
163+
datetime.time(h, m, s)
164+
for h in range(24)
165+
for m in range(60)
166+
for s in range(60)
167+
],
168+
list[datetime.time],
169+
id="time",
170+
),
171+
pytest.param(
172+
lambda: [None for _ in range(10_000)],
173+
list[None],
174+
id="None",
175+
),
176+
pytest.param(
177+
lambda: [False for _ in range(10_000)],
178+
list[Literal[False]],
179+
id="False",
180+
),
181+
pytest.param(
182+
lambda: [True for _ in range(10_000)],
183+
list[Literal[True]],
184+
id="True",
185+
),
186+
pytest.param(
187+
lambda: random.randbytes(10_000),
188+
bytes,
189+
id="bytes",
190+
),
191+
pytest.param(
192+
lambda: random.randbytes(10_000),
193+
bytearray,
194+
id="bytearray",
195+
),
196+
pytest.param(
197+
lambda: random.randbytes(10_000),
198+
memoryview,
199+
id="memoryview",
200+
),
201+
pytest.param(
202+
lambda: random.randbytes(10_000).hex(),
203+
str,
204+
id="long_string",
205+
),
206+
pytest.param(
207+
lambda: [[] * 10_000],
208+
list[list],
209+
id="list",
210+
),
211+
pytest.param(
212+
lambda: tuple(() * 10_000),
213+
tuple[tuple, ...],
214+
id="tuple",
215+
),
216+
pytest.param(
217+
lambda: [set() for _ in range(10_000)],
218+
list[set],
219+
id="set",
220+
),
221+
pytest.param(
222+
lambda: [frozenset() for _ in range(10_000)],
223+
list[frozenset],
224+
id="frozenset",
225+
),
226+
pytest.param(
227+
lambda: {str(i): i for i in range(10_000)},
228+
dict[str, int],
229+
id="dict",
230+
),
231+
pytest.param(
232+
lambda: [
233+
uuid.uuid5(uuid.NAMESPACE_DNS, f"test-{i}") for i in range(10_000)
234+
],
235+
list[uuid.UUID],
236+
id="uuid",
237+
),
238+
pytest.param(
239+
lambda: [random.choice(list(SomeEnum)) for _ in range(10_000)],
240+
list[SomeEnum],
241+
id="enum",
242+
),
243+
],
244+
)
245+
246+
247+
@sample_data_mark
248+
def test_decode_type(benchmark: BenchmarkFixture, proto, make_data, target_type):
249+
data = make_data()
250+
encoded = proto.encode(data)
251+
252+
res = benchmark.pedantic(
253+
proto.decode,
254+
args=(encoded,),
255+
kwargs={"type": target_type},
256+
warmup_rounds=10,
257+
)
258+
259+
assert res == data
260+
261+
262+
@sample_data_mark
263+
def test_encode_type(benchmark: BenchmarkFixture, proto, make_data, target_type):
264+
data = make_data()
265+
266+
res = benchmark.pedantic(
267+
proto.encode,
268+
args=(data,),
269+
warmup_rounds=10,
270+
)
271+
272+
assert res == proto.encode(data)
273+
274+
275+
@pytest.mark.parametrize(
276+
"make_data,target_type",
277+
[
278+
pytest.param(
279+
lambda: random.sample(range(-100_000, 100_000), 10_000), list[int], id="int"
280+
),
281+
pytest.param(
282+
lambda: [random.uniform(-100_000.0, 100_000.0) for _ in range(10_000)],
283+
list[float],
284+
id="float",
285+
),
286+
],
287+
)
288+
@pytest.mark.parametrize(
289+
"encode,decode",
290+
[
291+
pytest.param(msgspec.json.encode, msgspec.json.decode, id="json"),
292+
pytest.param(msgspec.msgpack.encode, msgspec.msgpack.decode, id="msgpack"),
293+
pytest.param(lambda v: v, msgspec.convert, id="convert"),
294+
],
295+
)
296+
def test_decode_convert_type_non_strict(
297+
benchmark: BenchmarkFixture, decode, encode, make_data, target_type
298+
):
299+
data = make_data()
300+
301+
encoded = encode(data)
302+
303+
res = benchmark.pedantic(
304+
decode,
305+
args=(encoded,),
306+
kwargs={"strict": False, "type": target_type},
307+
warmup_rounds=10,
308+
)
309+
310+
assert res == data

‎tests/prof/perf/test_module.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import subprocess
22
import sys
33

4+
import pytest
45

5-
def test_import_time(benchmark):
6+
7+
@pytest.mark.parametrize(
8+
"module_name",
9+
[
10+
"msgspec",
11+
"msgspec.structs",
12+
"msgspec.json",
13+
"msgspec.msgpack",
14+
"msgspec.inspect",
15+
"msgspec.yaml",
16+
"msgspec.toml",
17+
],
18+
)
19+
def test_import_time(benchmark, module_name):
620
# also measures interpreter startup etc., but is the best we can do now, since we
721
# don't have a fully isolated module, and some per-process global state, so
822
# re-importing purely within the current interpreter doesn't get reliable timings
923
def do_import():
10-
subprocess.run([sys.executable, "-c", "import msgspec"], check=True)
24+
subprocess.run([sys.executable, "-c", f"import {module_name}"], check=True)
1125

1226
do_import() # execute once to reduce possibility of a super cold FS cache
1327

‎tests/prof/perf/test_structs.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class C{n}(Struct, order=True):
1616
e: int
1717
"""
1818

19+
TEMPLATE_EMPTY = """
20+
class C{n}(Struct, order=True):
21+
pass
22+
"""
23+
1924
TEMPLATE_WITH_TAG_TRUE = """
2025
class C{n}(Struct, order=True, tag=True):
2126
a: int
@@ -54,9 +59,14 @@ class Item(msgspec.Struct, order=True):
5459
e: int
5560

5661

62+
class EmptyStruct(msgspec.Struct):
63+
pass
64+
65+
5766
@pytest.mark.parametrize(
5867
"template",
5968
[
69+
pytest.param(TEMPLATE_EMPTY, id="empty"),
6070
pytest.param(TEMPLATE, id="basic"),
6171
pytest.param(TEMPLATE_WITH_TAG_TRUE, id="tagged"),
6272
pytest.param(TEMPLATE_WITH_CUSTOM_TAG, id="custom_tag"),
@@ -109,6 +119,15 @@ def test_create(benchmark):
109119
)
110120

111121

122+
@pytest.mark.memory
123+
def test_empty_struct_create(benchmark: BenchmarkFixture):
124+
# empty struct to measure pure overhead without any type nodes
125+
benchmark.pedantic(
126+
lambda: [EmptyStruct() for _ in range(N)],
127+
warmup_rounds=10,
128+
)
129+
130+
112131
@pytest.mark.memory
113132
def test_equality(benchmark):
114133
needle = Item(N - 1, N - 1, N - 1, N - 1, N - 1)

0 commit comments

Comments
 (0)