fastsimdjson 0.6.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastsimdjson-0.6.0/src/fastsimdjson.egg-info → fastsimdjson-0.8.0}/PKG-INFO +87 -9
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/README.md +85 -8
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/pyproject.toml +2 -2
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.cpp +687 -51
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0/src/fastsimdjson.egg-info}/PKG-INFO +87 -9
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/requires.txt +1 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_dumps.py +112 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_stream.py +101 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/MANIFEST.in +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/setup.cfg +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/setup.py +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/SOURCES.txt +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/dependency_links.txt +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/top_level.txt +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_files.py +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_lazy.py +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_loads.py +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdjson.cpp +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdjson.h +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf.cpp +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf.h +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf_c.h +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/zmij-LICENSE +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/zmij.cc +0 -0
- {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/zmij.h +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastsimdjson
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Fast JSON parsing for Python, built on simdjson
|
|
5
5
|
Author-email: Daniel Lemire <daniel@lemire.me>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -25,6 +25,7 @@ Provides-Extra: test
|
|
|
25
25
|
Requires-Dist: pytest; extra == "test"
|
|
26
26
|
Provides-Extra: bench
|
|
27
27
|
Requires-Dist: orjson; extra == "bench"
|
|
28
|
+
Requires-Dist: jiter; extra == "bench"
|
|
28
29
|
Requires-Dist: msgspec; extra == "bench"
|
|
29
30
|
Requires-Dist: pysimdjson; extra == "bench"
|
|
30
31
|
Requires-Dist: cysimdjson; extra == "bench"
|
|
@@ -38,9 +39,11 @@ replacement for the `json` module's `loads`, `load`, `dumps` and `dump`, and
|
|
|
38
39
|
it can be over 3 times faster than the standard `json.loads` and
|
|
39
40
|
`json.dumps`. When you only need part of a document, its lazy `parse`
|
|
40
41
|
function is faster still. It also reads streams of documents (NDJSON, JSON
|
|
41
|
-
Lines)
|
|
42
|
+
Lines), on many threads with free-threaded Python, and can extract only the
|
|
43
|
+
fields you need from each document. It writes JSON in two ways: `dumps` returns the same `str` as
|
|
42
44
|
`json.dumps`, and `dumpb` returns the same `bytes` as `orjson.dumps`, as fast
|
|
43
|
-
as orjson
|
|
45
|
+
as orjson; `dumpb_many` writes NDJSON, on many threads with free-threaded
|
|
46
|
+
Python.
|
|
44
47
|
|
|
45
48
|
With pip:
|
|
46
49
|
|
|
@@ -156,6 +159,30 @@ serialize dataclasses, `datetime`, `date`, `time`, `UUID`, numpy arrays or
|
|
|
156
159
|
`OPT_PASSTHROUGH_DATACLASS` and `OPT_PASSTHROUGH_DATETIME` were set, and the
|
|
157
160
|
options that concern only these types are accepted and have no effect.
|
|
158
161
|
|
|
162
|
+
### `dumpb_many`: NDJSON, on several threads
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
data = fastsimdjson.dumpb_many(records) # b'{...}\n{...}\n'
|
|
166
|
+
data = fastsimdjson.dumpb_many(records, threads=16) # free-threaded Python
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`dumpb_many(records, default=None, option=None, *, threads=None)` returns
|
|
170
|
+
the records (a list, a tuple or any iterable) as NDJSON `bytes`: each record
|
|
171
|
+
as `dumpb` writes it, followed by a newline, so that the output reads back
|
|
172
|
+
with `loads_many(data, format="lines")`. `default` and `option` are those of
|
|
173
|
+
`dumpb`, except `OPT_INDENT_2`, which would spread a record over several
|
|
174
|
+
lines (`ValueError`); `OPT_APPEND_NEWLINE` changes nothing.
|
|
175
|
+
|
|
176
|
+
On free-threaded Python, `threads=N` writes chunks of records on N threads at
|
|
177
|
+
once (`threads=0` or `None`: one per processor), with the worker threads of
|
|
178
|
+
`map_many`. The output and the errors are those of one thread: when records
|
|
179
|
+
cannot be serialized, the error of the first of them is raised (`default` may
|
|
180
|
+
then have been called on later records, in other threads, and it must be
|
|
181
|
+
safe to call from several threads at once). With the GIL, the calling thread
|
|
182
|
+
does all the work. Like `dumpb`, `dumpb_many` does not crash when other
|
|
183
|
+
threads change the records meanwhile, but the output then reflects some of
|
|
184
|
+
the changes and not others.
|
|
185
|
+
|
|
159
186
|
### Files
|
|
160
187
|
|
|
161
188
|
```python
|
|
@@ -233,6 +260,37 @@ document, and the Python objects of all of them, to the calling thread.
|
|
|
233
260
|
With the GIL (the default build of Python), the calling thread does all the
|
|
234
261
|
work: `threads` has no effect on speed.
|
|
235
262
|
|
|
263
|
+
### Only some fields: `fields=`
|
|
264
|
+
|
|
265
|
+
When you need a few values from each document, `fields` extracts them
|
|
266
|
+
without building the documents: only the values found become Python
|
|
267
|
+
objects.
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
events = fastsimdjson.loads_many(data, fields=["/type", "/payload/action"])
|
|
271
|
+
# (type, action) for each document
|
|
272
|
+
|
|
273
|
+
def count(t):
|
|
274
|
+
kind, action, language = t
|
|
275
|
+
...
|
|
276
|
+
fastsimdjson.map_many(count, data, threads=64,
|
|
277
|
+
fields=["/type", "/payload/action",
|
|
278
|
+
"/payload/pull_request/base/repo/language"])
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`loads_many` and `map_many` accept `fields`: a JSON Pointer (RFC 6901), or a
|
|
282
|
+
list or tuple of them. Each document then becomes the value at the pointer,
|
|
283
|
+
or a tuple of the values at the pointers, in order. A value that is an
|
|
284
|
+
object or an array is built in full. A pointer that leads nowhere (a
|
|
285
|
+
missing key, an index past the end, or a step into a scalar) gives
|
|
286
|
+
`default`, `None` unless you pass another value; a JSON `null` gives `None`.
|
|
287
|
+
As with `at_pointer`, a key that appears twice in an object refers to its
|
|
288
|
+
first occurrence. `fields` cannot be combined with `lazy=True`.
|
|
289
|
+
|
|
290
|
+
On 7.6 GB of GitHub events, `map_many` with three fields ran at 2.8 GB/s on
|
|
291
|
+
one core of an Intel Xeon Gold 6548N, against 2.2 GB/s with lazy views
|
|
292
|
+
(`lazy=True`). With 64 threads, it ran at 78 GB/s (lazy views: 61 GB/s).
|
|
293
|
+
|
|
236
294
|
### `release`
|
|
237
295
|
|
|
238
296
|
`release()` frees the simdjson parser and the string caches kept by the
|
|
@@ -307,8 +365,8 @@ The suite builds an ~80 MB document and a list of 16,777,221 integers, so
|
|
|
307
365
|
give it some RAM.
|
|
308
366
|
|
|
309
367
|
The benchmark scripts need simdjson-data and the `bench` extra (orjson,
|
|
310
|
-
msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
311
|
-
`json.loads
|
|
368
|
+
jiter, msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
369
|
+
`json.loads`, orjson and jiter, `bench_lazy.py` times `parse` against pysimdjson and
|
|
312
370
|
cysimdjson, `bench_dumps.py` times `dumps` against `json.dumps` and `dumpb`
|
|
313
371
|
against orjson and msgspec, and `bench_many.py` times `loads_many`.
|
|
314
372
|
|
|
@@ -347,7 +405,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
347
405
|
|
|
348
406
|
<picture>
|
|
349
407
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads_dark.png">
|
|
350
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads.png" width="80%" alt="Parsing JSON in Python: fastsimdjson loads 0.78 GB/s, orjson 0.60, msgspec 0.53, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
408
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads.png" width="80%" alt="Parsing JSON in Python: fastsimdjson loads 0.78 GB/s, orjson 0.60, msgspec 0.53, jiter 0.48, jiter without its string cache 0.44, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
351
409
|
</picture>
|
|
352
410
|
|
|
353
411
|
| parser | GB/s | vs `json.loads` |
|
|
@@ -358,6 +416,8 @@ geometric mean over the 22 files (higher is better).
|
|
|
358
416
|
| ujson 6.0.0 | 0.37 | 1.66× |
|
|
359
417
|
| cysimdjson 26.27 | 0.43 | 1.96× |
|
|
360
418
|
| pysimdjson 7.0.2 | 0.44 | 1.98× |
|
|
419
|
+
| jiter 0.17.0 (no string cache) | 0.44 | 2.00× |
|
|
420
|
+
| jiter 0.17.0 (string cache, the default) | 0.48 | 2.21× |
|
|
361
421
|
| msgspec 0.22.0 | 0.53 | 2.42× |
|
|
362
422
|
| orjson 3.12.0 | 0.60 | 2.74× |
|
|
363
423
|
| **fastsimdjson `loads`** | **0.78** | **3.53×** |
|
|
@@ -371,7 +431,9 @@ gain comes from pausing the garbage collector while the objects are built.
|
|
|
371
431
|
Timing each call on its own, fastsimdjson's lead over orjson is 1.33× with
|
|
372
432
|
the collector enabled and 1.22× with it disabled (geometric means); without
|
|
373
433
|
the collector, `canada.json`, `mesh.json` and `numbers.json` are ties. yyjson 4.0.6 is left out: it
|
|
374
|
-
returns wrong strings for non-ASCII text.
|
|
434
|
+
returns wrong strings for non-ASCII text. jiter (the parser of pydantic)
|
|
435
|
+
caches strings by default; turning the cache off (`cache_mode=False`) makes it
|
|
436
|
+
slower here.
|
|
375
437
|
|
|
376
438
|
Parsing is no longer the bottleneck. simdjson alone parses these files at
|
|
377
439
|
3.1 GB/s. It accounts for about a third of the time of `loads`; the rest goes
|
|
@@ -386,12 +448,14 @@ and the screen name of the 100 statuses of `twitter.json`:
|
|
|
386
448
|
|
|
387
449
|
<picture>
|
|
388
450
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse_dark.png">
|
|
389
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse.png" width="80%" alt="Reading part of twitter.json: fastsimdjson parse 158 µs, pysimdjson 179, cysimdjson 229, msgspec 336, fastsimdjson loads 861, orjson 1009, json 3922">
|
|
451
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse.png" width="80%" alt="Reading part of twitter.json: fastsimdjson parse 158 µs, pysimdjson 179, cysimdjson 229, msgspec 336, fastsimdjson loads 861, orjson 1009, jiter 1281, jiter without its string cache 1592, json 3922">
|
|
390
452
|
</picture>
|
|
391
453
|
|
|
392
454
|
| method | µs |
|
|
393
455
|
|---|---:|
|
|
394
456
|
| `json.loads` | 3922 |
|
|
457
|
+
| jiter (no string cache) | 1592 |
|
|
458
|
+
| jiter (string cache) | 1281 |
|
|
395
459
|
| orjson | 1009 |
|
|
396
460
|
| fastsimdjson `loads` | 861 |
|
|
397
461
|
| msgspec (typed `Struct`) | 336 |
|
|
@@ -445,6 +509,18 @@ times faster than msgspec and 10 times faster than `json`. The comparison was
|
|
|
445
509
|
run on a processor with AVX-512, which `dumpb` and orjson both use to escape
|
|
446
510
|
strings; other x64 processors use SSE2 and ARM processors NEON.
|
|
447
511
|
|
|
512
|
+
### Writing NDJSON with `dumpb_many`
|
|
513
|
+
|
|
514
|
+
The 593,945 events of two hours of [GH Archive](https://www.gharchive.org/)
|
|
515
|
+
(1.87 GB of NDJSON), written back as NDJSON from Python objects. With the
|
|
516
|
+
GIL (Python 3.14.6), one thread: `orjson.dumps` on each record, joined, runs
|
|
517
|
+
at 0.94 GB/s, `dumpb` on each record at 1.07 GB/s, and `dumpb_many` at
|
|
518
|
+
1.41 GB/s. On free-threaded Python 3.14.6t, GB/s:
|
|
519
|
+
|
|
520
|
+
| threads | 1 | 8 | 32 | 64 |
|
|
521
|
+
|---|---:|---:|---:|---:|
|
|
522
|
+
| `dumpb_many(records, threads=N)` | 1.3 | 6.0 | 11 | 12 |
|
|
523
|
+
|
|
448
524
|
### Streams with `loads_many`
|
|
449
525
|
|
|
450
526
|
20 MB of NDJSON (5268 objects and arrays, one per line), made from the same
|
|
@@ -452,12 +528,14 @@ files; best of three runs:
|
|
|
452
528
|
|
|
453
529
|
<picture>
|
|
454
530
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson_dark.png">
|
|
455
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson.png" width="80%" alt="Reading NDJSON: fastsimdjson parse_many 0.94 GB/s, loads_many 0.38, msgspec decode_lines 0.37, fastsimdjson loads per line 0.33, msgspec decode per line 0.27, orjson per line 0.26, json per line 0.12">
|
|
531
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson.png" width="80%" alt="Reading NDJSON: fastsimdjson parse_many 0.94 GB/s, loads_many 0.38, msgspec decode_lines 0.37, fastsimdjson loads per line 0.33, msgspec decode per line 0.27, orjson per line 0.26, jiter per line 0.22, jiter without its string cache per line 0.20, json per line 0.12">
|
|
456
532
|
</picture>
|
|
457
533
|
|
|
458
534
|
| method | ms | GB/s |
|
|
459
535
|
|---|---:|---:|
|
|
460
536
|
| `json.loads` on each line | 169 | 0.12 |
|
|
537
|
+
| jiter on each line (no string cache) | 98 | 0.20 |
|
|
538
|
+
| jiter on each line (string cache) | 90 | 0.22 |
|
|
461
539
|
| orjson on each line | 77 | 0.26 |
|
|
462
540
|
| msgspec `decode` on each line | 74 | 0.27 |
|
|
463
541
|
| fastsimdjson `loads` on each line | 60 | 0.33 |
|
|
@@ -7,9 +7,11 @@ replacement for the `json` module's `loads`, `load`, `dumps` and `dump`, and
|
|
|
7
7
|
it can be over 3 times faster than the standard `json.loads` and
|
|
8
8
|
`json.dumps`. When you only need part of a document, its lazy `parse`
|
|
9
9
|
function is faster still. It also reads streams of documents (NDJSON, JSON
|
|
10
|
-
Lines)
|
|
10
|
+
Lines), on many threads with free-threaded Python, and can extract only the
|
|
11
|
+
fields you need from each document. It writes JSON in two ways: `dumps` returns the same `str` as
|
|
11
12
|
`json.dumps`, and `dumpb` returns the same `bytes` as `orjson.dumps`, as fast
|
|
12
|
-
as orjson
|
|
13
|
+
as orjson; `dumpb_many` writes NDJSON, on many threads with free-threaded
|
|
14
|
+
Python.
|
|
13
15
|
|
|
14
16
|
With pip:
|
|
15
17
|
|
|
@@ -125,6 +127,30 @@ serialize dataclasses, `datetime`, `date`, `time`, `UUID`, numpy arrays or
|
|
|
125
127
|
`OPT_PASSTHROUGH_DATACLASS` and `OPT_PASSTHROUGH_DATETIME` were set, and the
|
|
126
128
|
options that concern only these types are accepted and have no effect.
|
|
127
129
|
|
|
130
|
+
### `dumpb_many`: NDJSON, on several threads
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
data = fastsimdjson.dumpb_many(records) # b'{...}\n{...}\n'
|
|
134
|
+
data = fastsimdjson.dumpb_many(records, threads=16) # free-threaded Python
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`dumpb_many(records, default=None, option=None, *, threads=None)` returns
|
|
138
|
+
the records (a list, a tuple or any iterable) as NDJSON `bytes`: each record
|
|
139
|
+
as `dumpb` writes it, followed by a newline, so that the output reads back
|
|
140
|
+
with `loads_many(data, format="lines")`. `default` and `option` are those of
|
|
141
|
+
`dumpb`, except `OPT_INDENT_2`, which would spread a record over several
|
|
142
|
+
lines (`ValueError`); `OPT_APPEND_NEWLINE` changes nothing.
|
|
143
|
+
|
|
144
|
+
On free-threaded Python, `threads=N` writes chunks of records on N threads at
|
|
145
|
+
once (`threads=0` or `None`: one per processor), with the worker threads of
|
|
146
|
+
`map_many`. The output and the errors are those of one thread: when records
|
|
147
|
+
cannot be serialized, the error of the first of them is raised (`default` may
|
|
148
|
+
then have been called on later records, in other threads, and it must be
|
|
149
|
+
safe to call from several threads at once). With the GIL, the calling thread
|
|
150
|
+
does all the work. Like `dumpb`, `dumpb_many` does not crash when other
|
|
151
|
+
threads change the records meanwhile, but the output then reflects some of
|
|
152
|
+
the changes and not others.
|
|
153
|
+
|
|
128
154
|
### Files
|
|
129
155
|
|
|
130
156
|
```python
|
|
@@ -202,6 +228,37 @@ document, and the Python objects of all of them, to the calling thread.
|
|
|
202
228
|
With the GIL (the default build of Python), the calling thread does all the
|
|
203
229
|
work: `threads` has no effect on speed.
|
|
204
230
|
|
|
231
|
+
### Only some fields: `fields=`
|
|
232
|
+
|
|
233
|
+
When you need a few values from each document, `fields` extracts them
|
|
234
|
+
without building the documents: only the values found become Python
|
|
235
|
+
objects.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
events = fastsimdjson.loads_many(data, fields=["/type", "/payload/action"])
|
|
239
|
+
# (type, action) for each document
|
|
240
|
+
|
|
241
|
+
def count(t):
|
|
242
|
+
kind, action, language = t
|
|
243
|
+
...
|
|
244
|
+
fastsimdjson.map_many(count, data, threads=64,
|
|
245
|
+
fields=["/type", "/payload/action",
|
|
246
|
+
"/payload/pull_request/base/repo/language"])
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`loads_many` and `map_many` accept `fields`: a JSON Pointer (RFC 6901), or a
|
|
250
|
+
list or tuple of them. Each document then becomes the value at the pointer,
|
|
251
|
+
or a tuple of the values at the pointers, in order. A value that is an
|
|
252
|
+
object or an array is built in full. A pointer that leads nowhere (a
|
|
253
|
+
missing key, an index past the end, or a step into a scalar) gives
|
|
254
|
+
`default`, `None` unless you pass another value; a JSON `null` gives `None`.
|
|
255
|
+
As with `at_pointer`, a key that appears twice in an object refers to its
|
|
256
|
+
first occurrence. `fields` cannot be combined with `lazy=True`.
|
|
257
|
+
|
|
258
|
+
On 7.6 GB of GitHub events, `map_many` with three fields ran at 2.8 GB/s on
|
|
259
|
+
one core of an Intel Xeon Gold 6548N, against 2.2 GB/s with lazy views
|
|
260
|
+
(`lazy=True`). With 64 threads, it ran at 78 GB/s (lazy views: 61 GB/s).
|
|
261
|
+
|
|
205
262
|
### `release`
|
|
206
263
|
|
|
207
264
|
`release()` frees the simdjson parser and the string caches kept by the
|
|
@@ -276,8 +333,8 @@ The suite builds an ~80 MB document and a list of 16,777,221 integers, so
|
|
|
276
333
|
give it some RAM.
|
|
277
334
|
|
|
278
335
|
The benchmark scripts need simdjson-data and the `bench` extra (orjson,
|
|
279
|
-
msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
280
|
-
`json.loads
|
|
336
|
+
jiter, msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
337
|
+
`json.loads`, orjson and jiter, `bench_lazy.py` times `parse` against pysimdjson and
|
|
281
338
|
cysimdjson, `bench_dumps.py` times `dumps` against `json.dumps` and `dumpb`
|
|
282
339
|
against orjson and msgspec, and `bench_many.py` times `loads_many`.
|
|
283
340
|
|
|
@@ -316,7 +373,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
316
373
|
|
|
317
374
|
<picture>
|
|
318
375
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads_dark.png">
|
|
319
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads.png" width="80%" alt="Parsing JSON in Python: fastsimdjson loads 0.78 GB/s, orjson 0.60, msgspec 0.53, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
376
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads.png" width="80%" alt="Parsing JSON in Python: fastsimdjson loads 0.78 GB/s, orjson 0.60, msgspec 0.53, jiter 0.48, jiter without its string cache 0.44, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
320
377
|
</picture>
|
|
321
378
|
|
|
322
379
|
| parser | GB/s | vs `json.loads` |
|
|
@@ -327,6 +384,8 @@ geometric mean over the 22 files (higher is better).
|
|
|
327
384
|
| ujson 6.0.0 | 0.37 | 1.66× |
|
|
328
385
|
| cysimdjson 26.27 | 0.43 | 1.96× |
|
|
329
386
|
| pysimdjson 7.0.2 | 0.44 | 1.98× |
|
|
387
|
+
| jiter 0.17.0 (no string cache) | 0.44 | 2.00× |
|
|
388
|
+
| jiter 0.17.0 (string cache, the default) | 0.48 | 2.21× |
|
|
330
389
|
| msgspec 0.22.0 | 0.53 | 2.42× |
|
|
331
390
|
| orjson 3.12.0 | 0.60 | 2.74× |
|
|
332
391
|
| **fastsimdjson `loads`** | **0.78** | **3.53×** |
|
|
@@ -340,7 +399,9 @@ gain comes from pausing the garbage collector while the objects are built.
|
|
|
340
399
|
Timing each call on its own, fastsimdjson's lead over orjson is 1.33× with
|
|
341
400
|
the collector enabled and 1.22× with it disabled (geometric means); without
|
|
342
401
|
the collector, `canada.json`, `mesh.json` and `numbers.json` are ties. yyjson 4.0.6 is left out: it
|
|
343
|
-
returns wrong strings for non-ASCII text.
|
|
402
|
+
returns wrong strings for non-ASCII text. jiter (the parser of pydantic)
|
|
403
|
+
caches strings by default; turning the cache off (`cache_mode=False`) makes it
|
|
404
|
+
slower here.
|
|
344
405
|
|
|
345
406
|
Parsing is no longer the bottleneck. simdjson alone parses these files at
|
|
346
407
|
3.1 GB/s. It accounts for about a third of the time of `loads`; the rest goes
|
|
@@ -355,12 +416,14 @@ and the screen name of the 100 statuses of `twitter.json`:
|
|
|
355
416
|
|
|
356
417
|
<picture>
|
|
357
418
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse_dark.png">
|
|
358
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse.png" width="80%" alt="Reading part of twitter.json: fastsimdjson parse 158 µs, pysimdjson 179, cysimdjson 229, msgspec 336, fastsimdjson loads 861, orjson 1009, json 3922">
|
|
419
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_parse.png" width="80%" alt="Reading part of twitter.json: fastsimdjson parse 158 µs, pysimdjson 179, cysimdjson 229, msgspec 336, fastsimdjson loads 861, orjson 1009, jiter 1281, jiter without its string cache 1592, json 3922">
|
|
359
420
|
</picture>
|
|
360
421
|
|
|
361
422
|
| method | µs |
|
|
362
423
|
|---|---:|
|
|
363
424
|
| `json.loads` | 3922 |
|
|
425
|
+
| jiter (no string cache) | 1592 |
|
|
426
|
+
| jiter (string cache) | 1281 |
|
|
364
427
|
| orjson | 1009 |
|
|
365
428
|
| fastsimdjson `loads` | 861 |
|
|
366
429
|
| msgspec (typed `Struct`) | 336 |
|
|
@@ -414,6 +477,18 @@ times faster than msgspec and 10 times faster than `json`. The comparison was
|
|
|
414
477
|
run on a processor with AVX-512, which `dumpb` and orjson both use to escape
|
|
415
478
|
strings; other x64 processors use SSE2 and ARM processors NEON.
|
|
416
479
|
|
|
480
|
+
### Writing NDJSON with `dumpb_many`
|
|
481
|
+
|
|
482
|
+
The 593,945 events of two hours of [GH Archive](https://www.gharchive.org/)
|
|
483
|
+
(1.87 GB of NDJSON), written back as NDJSON from Python objects. With the
|
|
484
|
+
GIL (Python 3.14.6), one thread: `orjson.dumps` on each record, joined, runs
|
|
485
|
+
at 0.94 GB/s, `dumpb` on each record at 1.07 GB/s, and `dumpb_many` at
|
|
486
|
+
1.41 GB/s. On free-threaded Python 3.14.6t, GB/s:
|
|
487
|
+
|
|
488
|
+
| threads | 1 | 8 | 32 | 64 |
|
|
489
|
+
|---|---:|---:|---:|---:|
|
|
490
|
+
| `dumpb_many(records, threads=N)` | 1.3 | 6.0 | 11 | 12 |
|
|
491
|
+
|
|
417
492
|
### Streams with `loads_many`
|
|
418
493
|
|
|
419
494
|
20 MB of NDJSON (5268 objects and arrays, one per line), made from the same
|
|
@@ -421,12 +496,14 @@ files; best of three runs:
|
|
|
421
496
|
|
|
422
497
|
<picture>
|
|
423
498
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson_dark.png">
|
|
424
|
-
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson.png" width="80%" alt="Reading NDJSON: fastsimdjson parse_many 0.94 GB/s, loads_many 0.38, msgspec decode_lines 0.37, fastsimdjson loads per line 0.33, msgspec decode per line 0.27, orjson per line 0.26, json per line 0.12">
|
|
499
|
+
<img src="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_ndjson.png" width="80%" alt="Reading NDJSON: fastsimdjson parse_many 0.94 GB/s, loads_many 0.38, msgspec decode_lines 0.37, fastsimdjson loads per line 0.33, msgspec decode per line 0.27, orjson per line 0.26, jiter per line 0.22, jiter without its string cache per line 0.20, json per line 0.12">
|
|
425
500
|
</picture>
|
|
426
501
|
|
|
427
502
|
| method | ms | GB/s |
|
|
428
503
|
|---|---:|---:|
|
|
429
504
|
| `json.loads` on each line | 169 | 0.12 |
|
|
505
|
+
| jiter on each line (no string cache) | 98 | 0.20 |
|
|
506
|
+
| jiter on each line (string cache) | 90 | 0.22 |
|
|
430
507
|
| orjson on each line | 77 | 0.26 |
|
|
431
508
|
| msgspec `decode` on each line | 74 | 0.27 |
|
|
432
509
|
| fastsimdjson `loads` on each line | 60 | 0.33 |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastsimdjson"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Fast JSON parsing for Python, built on simdjson"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -34,7 +34,7 @@ Issues = "https://github.com/simdjson/fastpysimdjson/issues"
|
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
36
|
test = ["pytest"]
|
|
37
|
-
bench = ["orjson", "msgspec", "pysimdjson", "cysimdjson"]
|
|
37
|
+
bench = ["orjson", "jiter", "msgspec", "pysimdjson", "cysimdjson"]
|
|
38
38
|
|
|
39
39
|
[tool.cibuildwheel]
|
|
40
40
|
skip = ["pp*"]
|