<div dir="ltr">Making sequence a local variable does improve things (especially for ascii), but a substantial difference remains. It appears that the performance difference for ascii goes all the way back to jdk 11. The difference for non-ascii showed up in jdk 21. I wonder if this is related to the index checks?<br><br><span style="font-family:monospace">jdk 11<br><br>Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units<br>test        ascii        String  avgt    3  1137.348 ±   12.835  ns/op<br>test        ascii  StringBuffer  avgt    3   712.874 ±  509.320  ns/op<br>test    non-ascii        String  avgt    3   668.657 ±  246.550  ns/op<br>test    non-ascii  StringBuffer  avgt    3   897.344 ± 4353.414  ns/op<br><br><br>jdk 17<br>Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units<br>test        ascii        String  avgt    3  1321.497 ± 2107.466  ns/op<br>test        ascii  StringBuffer  avgt    3   715.936 ±  412.189  ns/op<br>test    non-ascii        String  avgt    3   722.986 ±  443.389  ns/op<br>test    non-ascii  StringBuffer  avgt    3   722.787 ±  771.816  ns/op</span><span style="font-family:monospace"><br><br><br>jdk 21<br>Benchmark  (data)      (source)  Mode  Cnt     Score       Error  Units<br>test        ascii        String  avgt    3  1150.301 ┬▒   918.549  ns/op<br>test        ascii  StringBuffer  avgt    3   713.183 ┬▒   543.850  ns/op<br>test    non-ascii        String  avgt    3  4642.667 ┬▒ 11481.029  ns/op<br>test    non-ascii  StringBuffer  avgt    3   728.027 ┬▒   936.521  ns/op<br><br><br>jdk 25<br>Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units<br>test        ascii        String  avgt    3  1184.513 ┬▒ 2057.498  ns/op<br>test        ascii  StringBuffer  avgt    3   786.611 ┬▒  411.657  ns/op<br>test    non-ascii        String  avgt    3  4197.585 ┬▒ 2761.388  ns/op<br>test    non-ascii  StringBuffer  avgt    3   716.375 ┬▒  815.349  ns/op<br><br><br>jdk 26<br>Benchmark  (data)      (source)  Mode  Cnt     Score     Error  Units<br>test        ascii        String  avgt    3  1107.207 ┬▒ 423.072  ns/op<br>test        ascii  StringBuffer  avgt    3   742.780 ┬▒ 178.890  ns/op<br>test    non-ascii        String  avgt    3  4043.914 ┬▒ 498.439  ns/op<br>test    non-ascii  StringBuffer  avgt    3   712.535 ┬▒ 583.255  ns/op</span><br></div><br><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Sat, Jul 19, 2025 at 4:17 PM Chen Liang <<a href="mailto:liangchenblue@gmail.com">liangchenblue@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr">Without looking at C2 IRs, I think there are a few potential culprits we can look into:</div><div>1. JDK-8351000 and JDK-8351443 updated StringBuilder</div><div>2. Sequence field is read in the loop; I wonder if making it an explicit immutable local variable changes anything here.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Jul 19, 2025 at 2:34 PM Brett Okken <<a href="mailto:brett.okken.os@gmail.com" target="_blank">brett.okken.os@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">I was looking at the performance of StringCharBuffer for various<br>
backing CharSequence types and was surprised to see a significant<br>
performance difference between String and StringBuffer. I wrote a<br>
small jmh which shows that the String implementation of charAt is<br>
significantly slower than StringBuilder. Is this expected?<br>
<br>
Benchmark                            (data)      (source)  Mode  Cnt<br>
  Score       Error  Units<br>
CharSequenceCharAtBenchmark.test      ascii        String  avgt    3<br>
2537.311 ┬▒  8952.197  ns/op<br>
CharSequenceCharAtBenchmark.test      ascii  StringBuffer  avgt    3<br>
852.004 ┬▒  2532.958  ns/op<br>
CharSequenceCharAtBenchmark.test  non-ascii        String  avgt    3<br>
5115.381 ┬▒ 13822.592  ns/op<br>
CharSequenceCharAtBenchmark.test  non-ascii  StringBuffer  avgt    3<br>
836.230 ┬▒  1154.191  ns/op<br>
<br>
<br>
<br>
@Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)<br>
@Warmup(iterations = 2, time = 7, timeUnit = TimeUnit.SECONDS)<br>
@BenchmarkMode(Mode.AverageTime)<br>
@OutputTimeUnit(TimeUnit.NANOSECONDS)<br>
@State(Scope.Benchmark)<br>
@Fork(value = 1, jvmArgsPrepend = {"-Xms512M", "-Xmx512M"})<br>
public class CharSequenceCharAtBenchmark {<br>
<br>
    @Param(value = {"ascii", "non-ascii"})<br>
    public String data;<br>
<br>
    @Param(value = {"String", "StringBuffer"})<br>
    public String source;<br>
<br>
    private CharSequence sequence;<br>
<br>
    @Setup(Level.Trial)<br>
    public void setup() throws Exception {<br>
        StringBuilder sb = new StringBuilder(3152);<br>
        for (int i=0; i<3152; ++i) {<br>
            char c = (char) i;<br>
            if ("ascii".equals(data)) {<br>
                c = (char) (i & 0x7f);<br>
            }<br>
            sb.append(c);<br>
        }<br>
<br>
        switch(source) {<br>
            case "String":<br>
                sequence = sb.toString();<br>
                break;<br>
            case "StringBuffer":<br>
                sequence = sb;<br>
                break;<br>
            default:<br>
                throw new IllegalArgumentException(source);<br>
        }<br>
    }<br>
<br>
    @Benchmark<br>
    public int test() {<br>
        int sum = 0;<br>
        for (int i=0, j=sequence.length(); i<j; ++i) {<br>
            sum += sequence.charAt(i);<br>
        }<br>
        return sum;<br>
    }<br>
}<br>
</blockquote></div></div>
</blockquote></div>