Skip to content

Commit 22baffb

Browse files
committed
fix: remove function call overhead + test "incorrect" implementations
1 parent 0ae4a90 commit 22baffb

3 files changed

Lines changed: 62 additions & 33 deletions

File tree

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Fatest JSON Python module
1+
# Fastest JSON Python module
22

33
Run Python JSON benchmarks to find the fastest module that can be used as a drop-in replacement of the `json` standard one.
44

@@ -14,12 +14,22 @@ See the [requirements.txt](requirements.txt) file for exact modules being tested
1414

1515
## Not An Option
1616

17-
- `cysimdjson`: not a drop-in replacement
18-
- `hyperjson`: no more maintained, and the author recommands `orjson`
19-
- `jsonlib2`: no more maintained
20-
- `libpy_simdjson`: no wheel provided
21-
- `metamagic.json`: no more maintained
22-
- `orjson`: not a drop-in replacement
23-
- `python-cjson`: no more maintained
24-
- `simdjson`: not a drop-in replacement
25-
- `yajl`: no more maintained
17+
### Incorrect Output
18+
19+
But they are still tested, in case their behavior changes over time.
20+
21+
- `orjson`
22+
- `pysimdjson`
23+
24+
### Not a Drop-In Replacement
25+
26+
- `cysimdjson`
27+
28+
### No More Maintained
29+
30+
- `hyperjson`
31+
- `jsonlib2`
32+
- `libpy_simdjson`
33+
- `metamagic.json`
34+
- `python-cjson`
35+
- `yajl`

bench-json.py

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
"""Run JSON benchmarks on given modules."""
2+
23
import sys
34
from collections import namedtuple
45
from timeit import timeit
5-
from types import ModuleType
6-
from typing import Any
76

87
# XXX: here you set which modules you want to benchmark
9-
MODULES_TO_TEST = ["fast_json", "pyjson5", "rapidjson", "simplejson", "ujson"]
8+
MODULES_TO_TEST = [
9+
"fast_json",
10+
"orjson",
11+
"pyjson5",
12+
"rapidjson",
13+
"simdjson",
14+
"simplejson",
15+
"ujson",
16+
]
1017
MODULE_REF = "json"
1118

1219
RAW = (
@@ -32,14 +39,6 @@
3239
Ref = namedtuple("Ref", "loads, dumps")
3340

3441

35-
def run_dumps(implementation: ModuleType) -> str:
36-
return implementation.dumps(FORMATED)
37-
38-
39-
def run_loads(implementation: ModuleType) -> dict[str, Any]:
40-
return implementation.loads(RAW)
41-
42-
4342
def run(stmt: str, setup: str) -> float:
4443
try:
4544
timeit(setup=setup, number=1)
@@ -77,22 +76,22 @@ def benchmark(*implementations: str) -> None:
7776

7877
for impl in implementations:
7978
loads = run(
80-
f"run_loads({impl})",
79+
"loads(RAW)",
8180
"; ".join(
8281
[
83-
f"import {impl}",
84-
"from __main__ import FORMATED, run_loads",
85-
f"assert run_loads({impl}) == FORMATED",
82+
f"from {impl} import loads",
83+
"from __main__ import FORMATED, RAW",
84+
"assert loads(RAW) == FORMATED",
8685
]
8786
),
8887
)
8988
dumps = run(
90-
f"run_dumps({impl})",
89+
"dumps(FORMATED)",
9190
"; ".join(
9291
[
93-
f"import {impl}",
94-
"from __main__ import run_dumps",
95-
f"assert isinstance(run_dumps({impl}), str)",
92+
f"from {impl} import dumps",
93+
"from __main__ import FORMATED",
94+
"assert isinstance(dumps(FORMATED), str)",
9695
]
9796
),
9897
)
@@ -101,20 +100,38 @@ def benchmark(*implementations: str) -> None:
101100
reference = Ref(loads, dumps)
102101
candidates.append((False, impl, loads, 1.0, dumps, 1.0))
103102
else:
104-
loads_coef = loads / reference.loads
105-
dumps_coef = dumps / reference.dumps
103+
loads_coef = loads / reference.loads or 10
104+
dumps_coef = dumps / reference.dumps or 10
106105
is_candidate = is_potential_candidate(loads_coef, dumps_coef)
107-
candidates.append((not is_candidate, impl, loads, loads_coef, dumps, dumps_coef))
106+
candidates.append(
107+
(not is_candidate, impl, loads, loads_coef, dumps, dumps_coef)
108+
)
108109

109110
signs = {None: "!", True: "+", False: "-"}
110111
fmt = "{} {} loads: {} {} | dumps: {} {}"
111112
for is_candidate, impl, loads, loads_coef, dumps, dumps_coef in sorted(
112113
candidates, key=lambda c: (c[0], c[3] + c[5], c[1])
113114
):
115+
if loads_coef >= 10:
116+
loads_coef = 0.0
117+
if dumps_coef >= 10:
118+
dumps_coef = 0.0
119+
if loads_coef + dumps_coef == 0.0:
120+
continue
121+
114122
# Ugly, I know. But to simplify the previous `sorted()` call, `is_candidate` is True for non candidate implementations
115123
is_candidate = not is_candidate
116124
sign = signs[is_candidate if impl != MODULE_REF else None]
117-
print(fmt.format(sign, impl.ljust(justify, "…"), res(loads), coef(loads_coef), res(dumps), coef(dumps_coef)))
125+
print(
126+
fmt.format(
127+
sign,
128+
impl.ljust(justify, "…"),
129+
res(loads),
130+
coef(loads_coef),
131+
res(dumps),
132+
coef(dumps_coef),
133+
)
134+
)
118135

119136

120137
def main() -> int:

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
fast-json==0.3.2
2+
orjson==3.10.15
23
pyjson5==1.6.8
4+
pysimdjson==6.0.2
35
python-rapidjson==1.20
46
simplejson==3.19.3
57
ujson==5.10.0

0 commit comments

Comments
 (0)