From fc2f5ea4742367641321bfce069a3e62f8008f63 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 11 Nov 2026 19:22:55 +0100 Subject: [PATCH] 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 --- docs/mkdocs/docs/api/basic_json/parse.md | 8 +++++--- docs/mkdocs/docs/api/basic_json/sax_parse.md | 7 ++++--- docs/mkdocs/docs/features/parsing/index.md | 2 +- docs/mkdocs/docs/features/performance.md | 5 +++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/parse.md b/docs/mkdocs/docs/api/basic_json/parse.md index 0ebcdb496..1ec40217c 100644 --- a/docs/mkdocs/docs/api/basic_json/parse.md +++ b/docs/mkdocs/docs/api/basic_json/parse.md @@ -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). diff --git a/docs/mkdocs/docs/api/basic_json/sax_parse.md b/docs/mkdocs/docs/api/basic_json/sax_parse.md index af2480e53..2951d4bfd 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -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 diff --git a/docs/mkdocs/docs/features/parsing/index.md b/docs/mkdocs/docs/features/parsing/index.md index 38e523f88..3fbc9e083 100644 --- a/docs/mkdocs/docs/features/parsing/index.md +++ b/docs/mkdocs/docs/features/parsing/index.md @@ -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`, `#!cpp std::istreambuf_iterator`), wide strings | one character at a time | Input in one block of memory is read even faster with the default options (no diff --git a/docs/mkdocs/docs/features/performance.md b/docs/mkdocs/docs/features/performance.md index b901e7ff3..62ebc5916 100644 --- a/docs/mkdocs/docs/features/performance.md +++ b/docs/mkdocs/docs/features/performance.md @@ -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).