FasterXML / FasterXML/jackson-core

Avoid per-character escape-table load in _writeStringSegment ASCII loop

Ouverte
#1,680 10 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Java
Étoiles
2.4k
Forks
928
Merge moyen
2 j 18 h
PR mergées (30 j)
24

Description

`_writeStringSegment()`'s ASCII fast loop reads `_outputEscapes[ch]` for every character:

```java
while (offset < len) {
int ch = text.charAt(offset);
if (ch > 0x7F || escCodes[ch] != 0) {
break;
}
outputBuffer[outputPtr++] = (byte) ch;
++offset;
}
```

That is a second, character-dependent load on the critical path of every character copied.

### Suggested fix

For the standard escape table the same test is expressible with constants
(`ch < 0x20 || ch > 0x7F || ch == '"' || ch == '\\'`), so the loop can branch on a loop-invariant
flag:

```java
final boolean stdEsc = (escCodes == CharTypes.get7BitOutputEscapes());
```

C2 unswitches the loop on it and constant-folds the flag in each clone, so the standard-table clone
has no per-character table load. A custom quote character, `ESCAPE_FORWARD_SLASHES`, or a
`CharacterEscapes` instance all produce a different array, so the flag is false and the original
lookup runs unchanged.

### Measurements

JDK 25 / x86_64, one String property per object, 3 forks, 5x5 iterations, pinned. Removing the load
shortens the loop body enough for C2 to unroll it twice as far:

| | insns | `(%rsp)` operands | unroll | insns/char | stack ops/char |
|---|---|---|---|---|---|
| without | 31 | 4 | x2 | 15.5 | 2.0 |
| with | 55 | 4 | **x4** | **13.8** | **1.0** |

`SingleBench.serialize`: 356.5 -> 347.5 ns/op (3.1.5), 331.9 -> 318.6 ns/op (2.22.0).

Output is byte-identical: verified over all characters 0x00-0x100, quotes, backslashes, control
characters, forward slash, non-ASCII, and strings crossing the segment boundary.

### Important note on the size of the win

Measure this **after** https://github.com/FasterXML/jackson-databind/issues/6182. With that issue
still present, `String.charAt`'s branch profile is polluted, the copy loop is neither unrolled nor
range-check eliminated, and this change appears to be worth about -23%. Almost all of that is the
pollution rather than the removed table load. With the profile clean it is worth a few percent, as
above.

### Reproducer

https://github.com/franz1981/c2-writestring-spill-jmh -- `SingleBench.serialize`. The README
documents the `perfasm` loop measurements and the before/after numbers. `master` is Jackson 2.22.0;
the `jackson3-write-paths` branch is the same benchmark on 3.1.5.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.