You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by "pavel-ptashyts (via GitHub)" <gi...@apache.org> on 2026/09/18 13:05:20 UTC
[I] Escaped strings are decoded one character at a time from the first escape to the end of the string (java, fory-json) [fory]
pavel-ptashyts opened a new issue, #4055:
URL: https://github.com/apache/fory/issues/4055
### What happens
In `fory-json`, once a string contains its first escape, the rest of that string is decoded one character
per loop iteration. The word scanner that found the escape is never resumed, so the cost of a string is
proportional to the distance from its **first** escape to its end, not to the number of escapes.
On documents dominated by one large escaped string this makes reading several times slower than Jackson,
on the same document and into the same model.
### Where
All three readers, 1.7.3:
- `org/apache/fory/json/reader/Utf8JsonReader.java` - `readStringStop` copies the prefix into
`stringDecodeBuffer` and hands the remainder to `readStringLatin1Tail`, which loops per character
(quote/escape/control/ascii discrimination, a capacity check, one store, an input-limit check) and never
returns to the `stringStopMask` word scan.
- `org/apache/fory/json/reader/Latin1JsonReader.java` - same shape.
- `org/apache/fory/json/reader/Utf16JsonReader.java` - same shape.
A second, independent effect is in `clear()`: a `stringDecodeBuffer` larger than
`RETAINED_STRING_DECODE_BUFFER_SIZE` (8192) is replaced by a fresh 8 KiB array after every parse. The reader
itself is pooled, so a document whose string is larger than 8 KiB re-grows the buffer by doubling on every
single parse, and the growth arrays plus the replacement are garbage each time.
### Measurements
Application harness (JDK 25, JMH-free: min of 7 rounds of 300 iterations, both libraries warmed over all
documents before anything is timed, rounds alternating between the libraries). Six real OpenRTB bid
responses, 12-110 KB, each roughly 95% one `adm` string holding VAST XML - pure ASCII, about 100 escaped
quotes, the **first** of them at character 14 of 20006.
Reading the six documents into the same model, `byte[]` in both cases:
| | fory 1.7.3 | Jackson 3 |
|---|---|---|
| ns/document, total | 567382 | 201662 |
| allocated bytes/document, total | 934976 | 511184 |
The cause is isolated by changing only the escapes inside that one string, keeping its length at 20006
characters:
| variant | fory ns | Jackson ns |
|---|---|---|
| no escapes at all | 7036 | 12385 |
| one escape, at the very end | 10104 | 10169 |
| one escape, at character 1 | 41833 | 13562 |
| the real document, 106 escapes | 42533 | 14610 |
With no escape Fory is 1.76x **faster** than Jackson. One escape at the end costs almost nothing. One escape
near the front costs the same as a hundred of them - because everything after the first one is decoded
character by character.
The declared field order and the generated reader's fast/slow path are **not** involved: crafting variants of
the same documents so the fast path never breaks, and so it breaks on the very first key, changes the timing
by less than the harness noise.
### Suggested fix
Two independent changes, both local to the readers:
1. In the escaped-string tail, after handling one stop character, reuse the existing word scanner
(`stringStopMask` / `asciiStringStopMask` / `utf16StringStopMask`) to find the next one and copy the plain
run in a single pass - `System.arraycopy` for the byte-backed readers, a packing loop for the utf16 reader
whose characters are two bytes wide. Roughly 20 lines per reader.
2. In `clear()`, keep a buffer the recent documents actually needed instead of shrinking unconditionally:
track the longest string decoded since the buffer was last sized and only shrink when the buffer is more
than twice that. A steady stream of large documents then stops re-growing the buffer, while a one-off
large document is still released.
Measured with both changes applied to 1.7.3, same harness and documents:
| | fory 1.7.3 | fory 1.7.3 + fix | Jackson 3 |
|---|---|---|---|
| ns/document, total | 567382 | 91074 | 202520 |
| allocated bytes/document, total | 934976 | 295664 | 511064 |
That is 6.2x against unpatched Fory, and it turns a 2.8x loss against Jackson into a 2.2x win. The
`fromString` paths improve as well: 4-6x for the latin1 reader and 1.4-3.5x for the utf16 one, measured on the
same documents.
Correctness was checked by reading 264 payloads through all three readers and comparing every decoded value
against Jackson's: escapes at every position of a string spanning several scanner words, for `\"`, `\\`, `\/`,
`\n`, `\t`, `é`, `中`; surrogate pairs; raw multi-byte UTF-8; escapes back to back; a string ending
immediately after an escape; and 30000-character payloads with escapes scattered at random. The suite passes
on unpatched 1.7.3 first, and still passes with the fix.
I have the patch and will open a pull request against `main`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
Re: [I] [Java] Escaped strings are decoded one character at a time from the first escape to the end of the string (java, fory-json) [fory]
Posted by "chaokunyang (via GitHub)" <gi...@apache.org>.
chaokunyang closed issue #4055: [Java] Escaped strings are decoded one character at a time from the first escape to the end of the string (java, fory-json)
URL: https://github.com/apache/fory/issues/4055
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org