You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@pekko.apache.org by "pjfanning (via GitHub)" <gi...@apache.org> on 2026/09/13 12:20:44 UTC
[PR] perf: copy ASCII bytes with the bulk String.getBytes instead of a charAt loop [pekko-http]
pjfanning opened a new pull request, #1299:
URL: https://github.com/apache/pekko-http/pull/1299
### Motivation
`EnhancedString.getAsciiBytes` (and `asciiBytes`, which delegates to it) copied a `String` into a `byte[]` one character at a time through `String.charAt`. On JDK 9+ compact strings every `charAt` call carries a coder branch (`isLatin1()`) plus a bounds check, so the loop shows up as per-character overhead on what is really a bulk copy. These bytes back every rendered status line, header-name prefix, `chunked`/`Keep-Alive`/`close` token, `LazyValueBytesRenderable`/`SingletonValueRenderable` model value, the multipart boundary needle and the `Sec-WebSocket-Accept` hash input.
### Modification
Use `String.getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)`, the JDK primitive that copies the low 8 bits of each char straight into an existing array. For a Latin-1 coded string it is a single `System.arraycopy`; for a UTF-16 coded string it is one tight truncating loop. It is `@Deprecated` (since 1.1, not for removal), so the method carries `@nowarn("cat=deprecation")`; the build otherwise turns deprecation warnings into errors.
`getBytes(ISO_8859_1)`/`getBytes(US_ASCII)` were rejected on purpose: they substitute `?` for chars above their range instead of truncating, which would silently change rendered output. Bit-exact truncation is also what the outgoing header CR/LF guard in `Rendering` relies on — it inspects rendered *bytes*, so a char whose low byte is `0x0D` must keep rendering as `0x0D` to be caught and discarded.
One behavioural relaxation: the old loop always filled the array up to `array.length`, so a string *shorter* than the remaining space threw `StringIndexOutOfBoundsException`. The new code copies `min(string.length, array.length - offset)` bytes. All callers pass exact-size arrays so nothing observes the difference, and the documented contract ("only the portion that fits is copied") is unchanged.
Adds `EnhancedStringSpec` pinning the ASCII mapping, low-8-bit truncation for non-Latin-1 chars (which forces the UTF-16 coder), offset handling and the short-array/past-the-end cases, plus `EnhancedStringBenchmark` in `http-bench-jmh`, which keeps the old `charAt` loop as a baseline.
### Result
JDK 17.0.19, Apple Silicon laptop, `-f 2 -wi 5 -i 8 -w 1 -r 2`, average time, lower is better:
| input | old `charAt` loop (`asciiBytes`) | `asciiBytes` | `getAsciiBytes` (into existing array) |
|---|---|---|---|
| `chunked` (7 chars) | 21.97 ± 8.18 ns | 16.00 ± 4.02 ns | 8.36 ± 0.67 ns |
| `Content-Type: application/json; charset=UTF-8\r\n` (48 chars) | 31.71 ± 4.34 ns | 16.37 ± 1.38 ns | 9.00 ± 0.23 ns |
| 1024 × `x` | 229.63 ± 25.53 ns | 151.66 ± 4.90 ns | 26.86 ± 6.52 ns |
`asciiBytes` still allocates the result array, which is most of what is left in that column; `getAsciiBytes` is the pure copy and is where the `arraycopy` fast path shows (~8.5x on the 1 KiB input). Rendered bytes are identical before and after.
### Tests
- `sbt "http-core/Test/testOnly org.apache.pekko.http.impl.util.EnhancedStringSpec"` — 7/7 pass on Scala 2.13.18 and 3.3.8
- `sbt "http-bench-jmh/Jmh/run -f 2 -wi 5 -i 8 -w 1 -r 2 EnhancedStringBenchmark"` — table above
- `sbt http-core/mimaReportBinaryIssues` — clean
- `scalafmt --mode diff-ref=origin/main`, `sbt headerCreateAll`, `git diff --check` — clean
### References
None - follow-up to the recent header parsing hardening work
--
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: notifications-unsubscribe@pekko.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@pekko.apache.org
For additional commands, e-mail: notifications-help@pekko.apache.org
Re: [PR] perf: copy ASCII bytes with the bulk String.getBytes instead of a charAt loop [pekko-http]
Posted by "pjfanning (via GitHub)" <gi...@apache.org>.
pjfanning merged PR #1299:
URL: https://github.com/apache/pekko-http/pull/1299
--
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: notifications-unsubscribe@pekko.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@pekko.apache.org
For additional commands, e-mail: notifications-help@pekko.apache.org