Return SubSequence from SQLCommenter.getFirstWord to avoid per-inject substring#11736
Conversation
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
Benchmark results (zulu-17, JDK 17.0.7,
|
| throughput | gc.alloc.rate.norm | |
|---|---|---|
| before (substring) | 258.1M ± 21.0M ops/s | 48 B/op |
| after (SubSequence) | 508.0M ± 21.6M ops/s | ~0 B/op (10⁻⁴) |
The SubSequence view is escape-analysis–elided in the transient firstWord check, so the old substring's 48 B/op (a String + its backing array) drops to ~0 and throughput rises ~2× at @Threads(8). @Fork(5) tightens the earlier bimodal @Fork(2) spread. Benchmark Javadoc refreshed in 89ca7b8.
…comparisons)
These are the ops a real classification parse needs but CharSequence lacks —
surfaced by porting SQLCommenter.getFirstWord (firstWord.startsWith("{"),
firstWord.equalsIgnoreCase("call")). Lets transient parse views be compared
without materializing a String. + JUnit 5 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… substring
getFirstWord's result is only consumed by startsWith("{") and
equalsIgnoreCase("call"), so materializing a substring on every inject() was
pure waste. Return a zero-copy SubSequence view instead and use its
startsWith/equalsIgnoreCase. Behavior is unchanged (91 SQLCommenterTest cases
pass); the test compares getFirstWord(sql).toString().
This is the motivating consumer for SubSequence.startsWith/equalsIgnoreCase
(retracted from #10640 and carried here so they land with their use case).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Benchmarks the getFirstWord first-word scan, consuming the result via the real transient pattern (startsWith, returning the boolean) so EA isn't faked away. @threads(8), @fork(2), -prof gc: the old substring allocated 48 B/op per call; the SubSequence view is fully EA-elided (~0 B/op), ~1.6x throughput. Numbers in the class Javadoc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89ca7b8 to
2f648c7
Compare
| * for large SQL). Consume it in place and discard; if the value must be retained, call {@link | ||
| * SubSequence#toString()} to detach a standalone copy. | ||
| */ | ||
| protected static SubSequence getFirstWord(String sql) { |
There was a problem hiding this comment.
Looks good for the generic SubSequence case to have extra helper methods such as startsWith and equalsIgnoreCase. Looking back at the concrete getFirstWord use cases, though, it could be much simpler: I would eliminate getFirstWord completely, since it does not seem to add much value here.
It is only used to skip whitespace and check whether the next token is either { or a case-insensitive call followed by whitespace. That could be checked directly in a small amount of code.
There was a problem hiding this comment.
Fair point. I've been erring on the side of keeping the changes 1-to-1 and very mechanical, but I do agree that further simplification might make sense here.
This comment has been minimized.
This comment has been minimized.
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
The #11736 CharSequence (charAt-loop) versions are superseded by the String-delegating overloads here; String-literal callers (SQLCommenter) bind to the String overloads. Removes the redundant pair + their tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What Does This Do
Updates
SQLCommenter.getFirstWordto return aSubSequenceinstead of asub-String.By using
SubSequence, we avoid the sub-string allocation and often theSubSequencewill be optimized away completely via escape analysis.Motivation
Reduce work in application threads
Reduce JDBC integration allocation
Additional Notes
SQLCommenter.getFirstWordreturns a zero-copySubSequenceinstead ofsql.substring(...). Its result is only consumed bystartsWith("{")/equalsIgnoreCase("call")ininject, so the per-injectsubstring allocation was pure waste. The returned view carries a "transient — do not retain" warning (it pins its backingString).It also carries
SubSequence.startsWith/equalsIgnoreCase(retracted from #10640 so they land with their motivating consumer rather than as speculative API).SQLCommenterGetFirstWordBenchmark(JDK 17,@Threads(8),@Fork(5),-prof gc)gc.alloc.rate.normsubstring)SubSequence)Escape analysis fully elides the view in the transient consumption: 48 B/op → ~0, ~2× throughput. (The win is contingent on non-escape — confirming why the view must not be retained.)
🤖 Generated with Claude Code