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.
Files changed (25) hide show
  1. {fastsimdjson-0.6.0/src/fastsimdjson.egg-info → fastsimdjson-0.8.0}/PKG-INFO +87 -9
  2. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/README.md +85 -8
  3. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/pyproject.toml +2 -2
  4. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.cpp +687 -51
  5. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0/src/fastsimdjson.egg-info}/PKG-INFO +87 -9
  6. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/requires.txt +1 -0
  7. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_dumps.py +112 -0
  8. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_stream.py +101 -0
  9. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/MANIFEST.in +0 -0
  10. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/setup.cfg +0 -0
  11. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/setup.py +0 -0
  12. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/SOURCES.txt +0 -0
  13. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/dependency_links.txt +0 -0
  14. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/src/fastsimdjson.egg-info/top_level.txt +0 -0
  15. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_files.py +0 -0
  16. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_lazy.py +0 -0
  17. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/tests/test_loads.py +0 -0
  18. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdjson.cpp +0 -0
  19. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdjson.h +0 -0
  20. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf.cpp +0 -0
  21. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf.h +0 -0
  22. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/simdutf_c.h +0 -0
  23. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/zmij-LICENSE +0 -0
  24. {fastsimdjson-0.6.0 → fastsimdjson-0.8.0}/vendor/zmij.cc +0 -0
  25. {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.6.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). It writes JSON in two ways: `dumps` returns the same `str` as
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` and orjson, `bench_lazy.py` times `parse` against pysimdjson and
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). It writes JSON in two ways: `dumps` returns the same `str` as
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` and orjson, `bench_lazy.py` times `parse` against pysimdjson and
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.6.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*"]