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?

jdk 11

Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units
test        ascii        String  avgt    3  1137.348 ±   12.835  ns/op
test        ascii  StringBuffer  avgt    3   712.874 ±  509.320  ns/op
test    non-ascii        String  avgt    3   668.657 ±  246.550  ns/op
test    non-ascii  StringBuffer  avgt    3   897.344 ± 4353.414  ns/op


jdk 17
Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units
test        ascii        String  avgt    3  1321.497 ± 2107.466  ns/op
test        ascii  StringBuffer  avgt    3   715.936 ±  412.189  ns/op
test    non-ascii        String  avgt    3   722.986 ±  443.389  ns/op
test    non-ascii  StringBuffer  avgt    3   722.787 ±  771.816  ns/op


jdk 21
Benchmark  (data)      (source)  Mode  Cnt     Score       Error  Units
test        ascii        String  avgt    3  1150.301 ┬▒   918.549  ns/op
test        ascii  StringBuffer  avgt    3   713.183 ┬▒   543.850  ns/op
test    non-ascii        String  avgt    3  4642.667 ┬▒ 11481.029  ns/op
test    non-ascii  StringBuffer  avgt    3   728.027 ┬▒   936.521  ns/op


jdk 25
Benchmark  (data)      (source)  Mode  Cnt     Score      Error  Units
test        ascii        String  avgt    3  1184.513 ┬▒ 2057.498  ns/op
test        ascii  StringBuffer  avgt    3   786.611 ┬▒  411.657  ns/op
test    non-ascii        String  avgt    3  4197.585 ┬▒ 2761.388  ns/op
test    non-ascii  StringBuffer  avgt    3   716.375 ┬▒  815.349  ns/op


jdk 26
Benchmark  (data)      (source)  Mode  Cnt     Score     Error  Units
test        ascii        String  avgt    3  1107.207 ┬▒ 423.072  ns/op
test        ascii  StringBuffer  avgt    3   742.780 ┬▒ 178.890  ns/op
test    non-ascii        String  avgt    3  4043.914 ┬▒ 498.439  ns/op
test    non-ascii  StringBuffer  avgt    3   712.535 ┬▒ 583.255  ns/op


On Sat, Jul 19, 2025 at 4:17 PM Chen Liang <liangchenb...@gmail.com> wrote:

> Without looking at C2 IRs, I think there are a few potential culprits we
> can look into:
> 1. JDK-8351000 and JDK-8351443 updated StringBuilder
> 2. Sequence field is read in the loop; I wonder if making it an explicit
> immutable local variable changes anything here.
>
> On Sat, Jul 19, 2025 at 2:34 PM Brett Okken <brett.okken...@gmail.com>
> wrote:
>
>> I was looking at the performance of StringCharBuffer for various
>> backing CharSequence types and was surprised to see a significant
>> performance difference between String and StringBuffer. I wrote a
>> small jmh which shows that the String implementation of charAt is
>> significantly slower than StringBuilder. Is this expected?
>>
>> Benchmark                            (data)      (source)  Mode  Cnt
>>   Score       Error  Units
>> CharSequenceCharAtBenchmark.test      ascii        String  avgt    3
>> 2537.311 ┬▒  8952.197  ns/op
>> CharSequenceCharAtBenchmark.test      ascii  StringBuffer  avgt    3
>> 852.004 ┬▒  2532.958  ns/op
>> CharSequenceCharAtBenchmark.test  non-ascii        String  avgt    3
>> 5115.381 ┬▒ 13822.592  ns/op
>> CharSequenceCharAtBenchmark.test  non-ascii  StringBuffer  avgt    3
>> 836.230 ┬▒  1154.191  ns/op
>>
>>
>>
>> @Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
>> @Warmup(iterations = 2, time = 7, timeUnit = TimeUnit.SECONDS)
>> @BenchmarkMode(Mode.AverageTime)
>> @OutputTimeUnit(TimeUnit.NANOSECONDS)
>> @State(Scope.Benchmark)
>> @Fork(value = 1, jvmArgsPrepend = {"-Xms512M", "-Xmx512M"})
>> public class CharSequenceCharAtBenchmark {
>>
>>     @Param(value = {"ascii", "non-ascii"})
>>     public String data;
>>
>>     @Param(value = {"String", "StringBuffer"})
>>     public String source;
>>
>>     private CharSequence sequence;
>>
>>     @Setup(Level.Trial)
>>     public void setup() throws Exception {
>>         StringBuilder sb = new StringBuilder(3152);
>>         for (int i=0; i<3152; ++i) {
>>             char c = (char) i;
>>             if ("ascii".equals(data)) {
>>                 c = (char) (i & 0x7f);
>>             }
>>             sb.append(c);
>>         }
>>
>>         switch(source) {
>>             case "String":
>>                 sequence = sb.toString();
>>                 break;
>>             case "StringBuffer":
>>                 sequence = sb;
>>                 break;
>>             default:
>>                 throw new IllegalArgumentException(source);
>>         }
>>     }
>>
>>     @Benchmark
>>     public int test() {
>>         int sum = 0;
>>         for (int i=0, j=sequence.length(); i<j; ++i) {
>>             sum += sequence.charAt(i);
>>         }
>>         return sum;
>>     }
>> }
>>
>

Reply via email to