fastsimdjson 0.5.0__tar.gz → 0.7.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.5.0/src/fastsimdjson.egg-info → fastsimdjson-0.7.0}/PKG-INFO +55 -17
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/README.md +53 -16
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/pyproject.toml +2 -2
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/src/fastsimdjson.cpp +606 -58
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0/src/fastsimdjson.egg-info}/PKG-INFO +55 -17
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/src/fastsimdjson.egg-info/requires.txt +1 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/tests/test_stream.py +194 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/MANIFEST.in +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/setup.cfg +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/setup.py +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/src/fastsimdjson.egg-info/SOURCES.txt +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/src/fastsimdjson.egg-info/dependency_links.txt +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/src/fastsimdjson.egg-info/top_level.txt +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/tests/test_dumps.py +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/tests/test_files.py +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/tests/test_lazy.py +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/tests/test_loads.py +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/simdjson.cpp +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/simdjson.h +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/simdutf.cpp +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/simdutf.h +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/simdutf_c.h +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/zmij-LICENSE +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.0}/vendor/zmij.cc +0 -0
- {fastsimdjson-0.5.0 → fastsimdjson-0.7.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.7.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,7 +39,8 @@ 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
45
|
as orjson.
|
|
44
46
|
|
|
@@ -225,12 +227,45 @@ threads. An exception raised by `fn` is raised again, for the first document
|
|
|
225
227
|
where it happened; `fn` may then have been called on later documents, in
|
|
226
228
|
other threads, and it must be safe to call from several threads at once.
|
|
227
229
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
The worker threads are started by the first call that needs them and kept
|
|
231
|
+
for later calls (they wait, detached from the interpreter, between calls,
|
|
232
|
+
and stop at exit). `map_many` scales best: each document is built, used and
|
|
233
|
+
freed in the same thread, and only `fn`'s results come back. `loads_many` must return every
|
|
230
234
|
document, and the Python objects of all of them, to the calling thread.
|
|
231
235
|
With the GIL (the default build of Python), the calling thread does all the
|
|
232
236
|
work: `threads` has no effect on speed.
|
|
233
237
|
|
|
238
|
+
### Only some fields: `fields=`
|
|
239
|
+
|
|
240
|
+
When you need a few values from each document, `fields` extracts them
|
|
241
|
+
without building the documents: only the values found become Python
|
|
242
|
+
objects.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
events = fastsimdjson.loads_many(data, fields=["/type", "/payload/action"])
|
|
246
|
+
# (type, action) for each document
|
|
247
|
+
|
|
248
|
+
def count(t):
|
|
249
|
+
kind, action, language = t
|
|
250
|
+
...
|
|
251
|
+
fastsimdjson.map_many(count, data, threads=64,
|
|
252
|
+
fields=["/type", "/payload/action",
|
|
253
|
+
"/payload/pull_request/base/repo/language"])
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`loads_many` and `map_many` accept `fields`: a JSON Pointer (RFC 6901), or a
|
|
257
|
+
list or tuple of them. Each document then becomes the value at the pointer,
|
|
258
|
+
or a tuple of the values at the pointers, in order. A value that is an
|
|
259
|
+
object or an array is built in full. A pointer that leads nowhere (a
|
|
260
|
+
missing key, an index past the end, or a step into a scalar) gives
|
|
261
|
+
`default`, `None` unless you pass another value; a JSON `null` gives `None`.
|
|
262
|
+
As with `at_pointer`, a key that appears twice in an object refers to its
|
|
263
|
+
first occurrence. `fields` cannot be combined with `lazy=True`.
|
|
264
|
+
|
|
265
|
+
On 7.6 GB of GitHub events, `map_many` with three fields ran at 2.8 GB/s on
|
|
266
|
+
one core of an Intel Xeon Gold 6548N, against 2.2 GB/s with lazy views
|
|
267
|
+
(`lazy=True`). With 64 threads, it ran at 78 GB/s (lazy views: 61 GB/s).
|
|
268
|
+
|
|
234
269
|
### `release`
|
|
235
270
|
|
|
236
271
|
`release()` frees the simdjson parser and the string caches kept by the
|
|
@@ -305,8 +340,8 @@ The suite builds an ~80 MB document and a list of 16,777,221 integers, so
|
|
|
305
340
|
give it some RAM.
|
|
306
341
|
|
|
307
342
|
The benchmark scripts need simdjson-data and the `bench` extra (orjson,
|
|
308
|
-
msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
309
|
-
`json.loads
|
|
343
|
+
jiter, msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
344
|
+
`json.loads`, orjson and jiter, `bench_lazy.py` times `parse` against pysimdjson and
|
|
310
345
|
cysimdjson, `bench_dumps.py` times `dumps` against `json.dumps` and `dumpb`
|
|
311
346
|
against orjson and msgspec, and `bench_many.py` times `loads_many`.
|
|
312
347
|
|
|
@@ -345,7 +380,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
345
380
|
|
|
346
381
|
<picture>
|
|
347
382
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads_dark.png">
|
|
348
|
-
<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">
|
|
383
|
+
<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, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
349
384
|
</picture>
|
|
350
385
|
|
|
351
386
|
| parser | GB/s | vs `json.loads` |
|
|
@@ -356,6 +391,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
356
391
|
| ujson 6.0.0 | 0.37 | 1.66× |
|
|
357
392
|
| cysimdjson 26.27 | 0.43 | 1.96× |
|
|
358
393
|
| pysimdjson 7.0.2 | 0.44 | 1.98× |
|
|
394
|
+
| jiter 0.17.0 | 0.48 | 2.21× |
|
|
359
395
|
| msgspec 0.22.0 | 0.53 | 2.42× |
|
|
360
396
|
| orjson 3.12.0 | 0.60 | 2.74× |
|
|
361
397
|
| **fastsimdjson `loads`** | **0.78** | **3.53×** |
|
|
@@ -369,7 +405,9 @@ gain comes from pausing the garbage collector while the objects are built.
|
|
|
369
405
|
Timing each call on its own, fastsimdjson's lead over orjson is 1.33× with
|
|
370
406
|
the collector enabled and 1.22× with it disabled (geometric means); without
|
|
371
407
|
the collector, `canada.json`, `mesh.json` and `numbers.json` are ties. yyjson 4.0.6 is left out: it
|
|
372
|
-
returns wrong strings for non-ASCII text.
|
|
408
|
+
returns wrong strings for non-ASCII text. jiter (the parser of pydantic)
|
|
409
|
+
caches strings by default; turning the cache off (`cache_mode=False`) makes it
|
|
410
|
+
slower here (0.44 GB/s).
|
|
373
411
|
|
|
374
412
|
Parsing is no longer the bottleneck. simdjson alone parses these files at
|
|
375
413
|
3.1 GB/s. It accounts for about a third of the time of `loads`; the rest goes
|
|
@@ -475,15 +513,15 @@ disabled (see below), Intel Xeon Gold 6548N (64 cores); GB/s:
|
|
|
475
513
|
|
|
476
514
|
| threads | 1 | 2 | 4 | 8 | 16 | 32 | 64 |
|
|
477
515
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
|
478
|
-
| `map_many(len, data, threads=N)` |
|
|
479
|
-
| `parse_many(data, threads=N)` |
|
|
480
|
-
| `loads_many(data, threads=N)` | 0.
|
|
481
|
-
|
|
482
|
-
`map_many` is
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
without threads.
|
|
516
|
+
| `map_many(len, data, threads=N)` | 1.03 | 1.78 | 3.66 | 6.82 | 13.0 | 23.7 | **28.8** |
|
|
517
|
+
| `parse_many(data, threads=N)` | 2.00 | 2.84 | 5.28 | 9.26 | 16.1 | 22.7 | 25.8 |
|
|
518
|
+
| `loads_many(data, threads=N)` | 0.90 | 1.44 | 2.79 | 4.99 | 8.98 | 13.3 | 15.4 |
|
|
519
|
+
|
|
520
|
+
`map_many` is 28 times faster on 64 threads than on one (up to 38 GB/s when
|
|
521
|
+
nothing else runs in the process). `loads_many` builds every document for
|
|
522
|
+
the calling thread and is 17 times faster. With the collector enabled,
|
|
523
|
+
Python collects after the call returns: it examines the millions of objects
|
|
524
|
+
just created, which takes about 0.1 s here, with or without threads.
|
|
487
525
|
|
|
488
526
|
## Limitations
|
|
489
527
|
|
|
@@ -7,7 +7,8 @@ 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
13
|
as orjson.
|
|
13
14
|
|
|
@@ -194,12 +195,45 @@ threads. An exception raised by `fn` is raised again, for the first document
|
|
|
194
195
|
where it happened; `fn` may then have been called on later documents, in
|
|
195
196
|
other threads, and it must be safe to call from several threads at once.
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
The worker threads are started by the first call that needs them and kept
|
|
199
|
+
for later calls (they wait, detached from the interpreter, between calls,
|
|
200
|
+
and stop at exit). `map_many` scales best: each document is built, used and
|
|
201
|
+
freed in the same thread, and only `fn`'s results come back. `loads_many` must return every
|
|
199
202
|
document, and the Python objects of all of them, to the calling thread.
|
|
200
203
|
With the GIL (the default build of Python), the calling thread does all the
|
|
201
204
|
work: `threads` has no effect on speed.
|
|
202
205
|
|
|
206
|
+
### Only some fields: `fields=`
|
|
207
|
+
|
|
208
|
+
When you need a few values from each document, `fields` extracts them
|
|
209
|
+
without building the documents: only the values found become Python
|
|
210
|
+
objects.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
events = fastsimdjson.loads_many(data, fields=["/type", "/payload/action"])
|
|
214
|
+
# (type, action) for each document
|
|
215
|
+
|
|
216
|
+
def count(t):
|
|
217
|
+
kind, action, language = t
|
|
218
|
+
...
|
|
219
|
+
fastsimdjson.map_many(count, data, threads=64,
|
|
220
|
+
fields=["/type", "/payload/action",
|
|
221
|
+
"/payload/pull_request/base/repo/language"])
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`loads_many` and `map_many` accept `fields`: a JSON Pointer (RFC 6901), or a
|
|
225
|
+
list or tuple of them. Each document then becomes the value at the pointer,
|
|
226
|
+
or a tuple of the values at the pointers, in order. A value that is an
|
|
227
|
+
object or an array is built in full. A pointer that leads nowhere (a
|
|
228
|
+
missing key, an index past the end, or a step into a scalar) gives
|
|
229
|
+
`default`, `None` unless you pass another value; a JSON `null` gives `None`.
|
|
230
|
+
As with `at_pointer`, a key that appears twice in an object refers to its
|
|
231
|
+
first occurrence. `fields` cannot be combined with `lazy=True`.
|
|
232
|
+
|
|
233
|
+
On 7.6 GB of GitHub events, `map_many` with three fields ran at 2.8 GB/s on
|
|
234
|
+
one core of an Intel Xeon Gold 6548N, against 2.2 GB/s with lazy views
|
|
235
|
+
(`lazy=True`). With 64 threads, it ran at 78 GB/s (lazy views: 61 GB/s).
|
|
236
|
+
|
|
203
237
|
### `release`
|
|
204
238
|
|
|
205
239
|
`release()` frees the simdjson parser and the string caches kept by the
|
|
@@ -274,8 +308,8 @@ The suite builds an ~80 MB document and a list of 16,777,221 integers, so
|
|
|
274
308
|
give it some RAM.
|
|
275
309
|
|
|
276
310
|
The benchmark scripts need simdjson-data and the `bench` extra (orjson,
|
|
277
|
-
msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
278
|
-
`json.loads
|
|
311
|
+
jiter, msgspec, pysimdjson, cysimdjson). `bench.py` times `loads` against
|
|
312
|
+
`json.loads`, orjson and jiter, `bench_lazy.py` times `parse` against pysimdjson and
|
|
279
313
|
cysimdjson, `bench_dumps.py` times `dumps` against `json.dumps` and `dumpb`
|
|
280
314
|
against orjson and msgspec, and `bench_many.py` times `loads_many`.
|
|
281
315
|
|
|
@@ -314,7 +348,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
314
348
|
|
|
315
349
|
<picture>
|
|
316
350
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/simdjson/fastpysimdjson/main/doc/perf_loads_dark.png">
|
|
317
|
-
<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">
|
|
351
|
+
<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, pysimdjson 0.44, cysimdjson 0.43, ujson 0.37, python-rapidjson 0.24, simplejson 0.23, json 0.22">
|
|
318
352
|
</picture>
|
|
319
353
|
|
|
320
354
|
| parser | GB/s | vs `json.loads` |
|
|
@@ -325,6 +359,7 @@ geometric mean over the 22 files (higher is better).
|
|
|
325
359
|
| ujson 6.0.0 | 0.37 | 1.66× |
|
|
326
360
|
| cysimdjson 26.27 | 0.43 | 1.96× |
|
|
327
361
|
| pysimdjson 7.0.2 | 0.44 | 1.98× |
|
|
362
|
+
| jiter 0.17.0 | 0.48 | 2.21× |
|
|
328
363
|
| msgspec 0.22.0 | 0.53 | 2.42× |
|
|
329
364
|
| orjson 3.12.0 | 0.60 | 2.74× |
|
|
330
365
|
| **fastsimdjson `loads`** | **0.78** | **3.53×** |
|
|
@@ -338,7 +373,9 @@ gain comes from pausing the garbage collector while the objects are built.
|
|
|
338
373
|
Timing each call on its own, fastsimdjson's lead over orjson is 1.33× with
|
|
339
374
|
the collector enabled and 1.22× with it disabled (geometric means); without
|
|
340
375
|
the collector, `canada.json`, `mesh.json` and `numbers.json` are ties. yyjson 4.0.6 is left out: it
|
|
341
|
-
returns wrong strings for non-ASCII text.
|
|
376
|
+
returns wrong strings for non-ASCII text. jiter (the parser of pydantic)
|
|
377
|
+
caches strings by default; turning the cache off (`cache_mode=False`) makes it
|
|
378
|
+
slower here (0.44 GB/s).
|
|
342
379
|
|
|
343
380
|
Parsing is no longer the bottleneck. simdjson alone parses these files at
|
|
344
381
|
3.1 GB/s. It accounts for about a third of the time of `loads`; the rest goes
|
|
@@ -444,15 +481,15 @@ disabled (see below), Intel Xeon Gold 6548N (64 cores); GB/s:
|
|
|
444
481
|
|
|
445
482
|
| threads | 1 | 2 | 4 | 8 | 16 | 32 | 64 |
|
|
446
483
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
|
447
|
-
| `map_many(len, data, threads=N)` |
|
|
448
|
-
| `parse_many(data, threads=N)` |
|
|
449
|
-
| `loads_many(data, threads=N)` | 0.
|
|
450
|
-
|
|
451
|
-
`map_many` is
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
without threads.
|
|
484
|
+
| `map_many(len, data, threads=N)` | 1.03 | 1.78 | 3.66 | 6.82 | 13.0 | 23.7 | **28.8** |
|
|
485
|
+
| `parse_many(data, threads=N)` | 2.00 | 2.84 | 5.28 | 9.26 | 16.1 | 22.7 | 25.8 |
|
|
486
|
+
| `loads_many(data, threads=N)` | 0.90 | 1.44 | 2.79 | 4.99 | 8.98 | 13.3 | 15.4 |
|
|
487
|
+
|
|
488
|
+
`map_many` is 28 times faster on 64 threads than on one (up to 38 GB/s when
|
|
489
|
+
nothing else runs in the process). `loads_many` builds every document for
|
|
490
|
+
the calling thread and is 17 times faster. With the collector enabled,
|
|
491
|
+
Python collects after the call returns: it examines the millions of objects
|
|
492
|
+
just created, which takes about 0.1 s here, with or without threads.
|
|
456
493
|
|
|
457
494
|
## Limitations
|
|
458
495
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastsimdjson"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.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*"]
|