docs: fix the FILE* notes of parse() and the locking promise

The performance note of parse() said a FILE* is read one character at a
time. With glibc and the Apple C library the characters in the FILE's
buffer are scanned in bulk; only elsewhere is it one character at a
time. The file is locked once only where the C library has unlocked
stdio functions (not on musl, MinGW, newlib, with
JSON_DISABLE_UNLOCKED_FILE_IO, or under MSan). Qualify the promise on
parse.md, sax_parse.md, the parsing page and the performance page.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-11-11 19:22:55 +01:00
1 parent aaf63cbff6
commit fc2f5ea474
4 files changed
+13 -9

No files matched your search

+5 -3
View File
@@ -130,9 +130,11 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
`#!cpp std::istream` comes close: the parser scans the characters its stream buffer holds in bulk, without reading
ahead, so the stream is left exactly where it would be after reading one character at a time. A `#!cpp FILE*` is
read one character at a time, but the file is locked only once, so other threads that use the same `FILE` object
wait until parsing is done. See [input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
ahead, so the stream is left exactly where it would be after reading one character at a time. With glibc
and the Apple C library, the characters the `#!cpp FILE` holds in its buffer are scanned in bulk like a stream's;
elsewhere a `#!cpp FILE*` is read one character at a time. Where the C library has unlocked stdio functions, the
file is locked only once, so other threads that use the same `FILE` object wait until parsing is done. See
[input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
[`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md), and
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
+4 -3
View File
@@ -124,9 +124,10 @@ consumer `sax` has a super-linear complexity.
A UTF-8 byte order mark is silently ignored.
When reading from a `#!c FILE*`, the file is locked while it is parsed. The lock is recursive, so the SAX handler may
use the same file with stdio functions, but it must not wait for another thread that uses the same `FILE` object; see
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
When reading from a `#!c FILE*`, where the C library has unlocked stdio functions (see
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)), the file is locked while it is parsed.
The lock is recursive, so the SAX handler may use the same file with stdio functions, but it must not wait for another
thread that uses the same `FILE` object.
## Examples
+1 -1
View File
@@ -77,7 +77,7 @@ them faster than others:
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
| `#!cpp FILE*` | with glibc and the Apple C library, the characters the `FILE` holds in its buffer are scanned in bulk like a stream's; elsewhere one character at a time; the file is locked once for the whole parse |
| `#!cpp FILE*` | with glibc and the Apple C library, the characters the `FILE` holds in its buffer are scanned in bulk like a stream's; elsewhere one character at a time; where the C library has unlocked stdio functions, the file is locked once for the whole parse |
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
Input in one block of memory is read even faster with the default options (no
+3 -2
View File
@@ -27,8 +27,9 @@ A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scan
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
area is read one character at a time. A `#!cpp FILE*` is locked once per parse instead of once per character, and with
glibc and the Apple C library, the characters in its buffer are scanned in bulk as a stream's get area is. See
area is read one character at a time. Where the C library has unlocked stdio functions, a `#!cpp FILE*` is locked once
per parse instead of once per character, and with glibc and the Apple C library, the characters in its buffer are
scanned in bulk as a stream's get area is. See
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. Parsing from a
stream takes about 1.15 to 1.45 times as long as parsing an equivalent `#!cpp std::string`, which `parse()` reads with
a parser of its own (see there).