String.charAt vs StringBuilder.charAt performance

Johannes Döbler jd at civilian-framework.org
Thu Jul 24 17:33:40 UTC 2025


Hi Brett,

I ran your benchmark on a Windows11 Pro 24H2 / ARM64 / JDK 21 system  
and can reproduce your findings that 
CharSequenceCharAtBenchmark.testString<ascii> has weaker performance.
I also added these benchmarks:
     @Benchmark
     public int testStringBuilderAsCS() {
         return test(this.stringBuilder);
     }

     @Benchmark
     public int testStringAsCS() {
         return test(this.string);
     }

     private int test(CharSequence sequence) {
         int sum = 0;
         for (int i=0, j=sequence.length(); i<j; ++i) {
             sum += sequence.charAt(i);
         }
         return sum;
     }

and see a total breakdown of CharSequenceCharAtBenchmark.testStringAsCS

Benchmark (data)  Mode  Cnt     Score     Error  Units
CharSequenceCharAtBenchmark.testString                 ascii avgt    5  
1647,439 ±  70,486  ns/op
CharSequenceCharAtBenchmark.testString             non-ascii avgt    5   
939,780 ±  63,896  ns/op
CharSequenceCharAtBenchmark.testStringAsCS             ascii avgt    5  
1657,796 ±  18,488  ns/op
CharSequenceCharAtBenchmark.testStringAsCS         non-ascii avgt    5  
9400,447 ± 290,066  ns/op
CharSequenceCharAtBenchmark.testStringBuilder          ascii avgt    5   
923,943 ±   6,130  ns/op
CharSequenceCharAtBenchmark.testStringBuilder      non-ascii avgt    5   
941,507 ±  30,786  ns/op
CharSequenceCharAtBenchmark.testStringBuilderAsCS      ascii avgt    5   
930,974 ±  33,187  ns/op
CharSequenceCharAtBenchmark.testStringBuilderAsCS  non-ascii avgt    5   
945,983 ±  87,636  ns/op

Best
Johannes

On 22/07/2025 13:44, Brett Okken wrote:
> It does look like this is windows specific. If I run on WSL, I get 
> results similar to your linux-x64:
>
> Benchmark                       (data)  Mode  Cnt    Score     Error 
>  Units
> CharSequenceCharAtBenchmark.testString             ascii  avgt    3 
>  679.294 ± 302.947  ns/op
> CharSequenceCharAtBenchmark.testString         non-ascii  avgt    3 
>  702.071 ± 926.959  ns/op
> CharSequenceCharAtBenchmark.testStringBuilder      ascii  avgt    3 
>  682.815 ± 301.649  ns/op
> CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt    3 
>  678.169 ± 810.276  ns/op
>
> And I go back to the original version of the test, where String vs 
> StringBuilder is defined by parameter and both assigned to same local 
> variable as part of set up, that also shows no difference in wsl.
> So it appears everything I observed is an artifact of Windows specific 
> intrinsics?
>
> Benchmark              (data)       (source)  Mode  Cnt    Score Error 
>  Units
> CharSequenceCharAtBenchmark.test      ascii         String  avgt    3 
>  660.597 ± 146.405  ns/op
> CharSequenceCharAtBenchmark.test      ascii  StringBuilder  avgt    3 
>  659.395 ± 155.167  ns/op
> CharSequenceCharAtBenchmark.test  non-ascii         String  avgt    3 
>  647.955 ± 189.747  ns/op
> CharSequenceCharAtBenchmark.test  non-ascii  StringBuilder  avgt    3 
>  639.678 ± 146.923  ns/op
>
> On Mon, Jul 21, 2025 at 5:13 PM Brett Okken <brett.okken.os at gmail.com> 
> wrote:
>
>     I am running Windows x64. Windows 11 Pro 24H2
>
>     Intel(R) Core(TM) i7-1370P
>
>     On Mon, Jul 21, 2025 at 4:59 PM Chen Liang
>     <chen.l.liang at oracle.com> wrote:
>
>         I finally came around and ran the benchmark on my linux-x64
>         device; however, I could not produce your results where String
>         is significantly slower than StringBuilder.
>
>         This is the results I've got:
>
>         Benchmark (data)  Mode  Cnt    Score    Error  Units
>         CharSequenceCharAtBenchmark.testString ascii  avgt    5
>          668.649 ± 13.895  ns/op
>         CharSequenceCharAtBenchmark.testString non-ascii  avgt    5
>          651.903 ±  7.240  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  ascii  avgt  
>          5  673.802 ± 26.260  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt
>            5  657.374 ± 35.785  ns/op
>
>         I think we might have more clue - are you testing on a
>         macosx-aarch64 machine or some other platform? It might be
>         that on some platforms, there are some problems in the
>         hand-written assemblies for the intrinsics which contribute to
>         this slowdown, instead of a problem with the C2 IR.
>
>         Chen
>         ------------------------------------------------------------------------
>         *From:* core-libs-dev <core-libs-dev-retn at openjdk.org> on
>         behalf of Brett Okken <brett.okken.os at gmail.com>
>         *Sent:* Monday, July 21, 2025 4:01 PM
>         *To:* Roger Riggs <roger.riggs at oracle.com>
>         *Cc:* core-libs-dev at openjdk.org <core-libs-dev at openjdk.org>
>         *Subject:* Re: String.charAt vs StringBuilder.charAt performance
>         Updating to have different test methods for each
>         representation did remove the difference for the non-ascii
>         String case for the jdk 21+ releases.
>         However, the ascii (latin) strings are still slower with
>         String than StringBuilder.
>
>         How does C2 then handle something like StringCharBuffer
>         wrapping a CharSequence for all of it's get operations:
>         https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/nio/StringCharBuffer.java#L88-L97
>
>         Which is then used by CharBufferSpliterator
>         https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/nio/CharBufferSpliterator.java
>
>         And by many CharsetEncoder impls when either source or
>         destination is not backed by array (which would be the case if
>         StringCharBuffer used):
>         https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/sun/nio/cs/UTF_8.java#L517
>         https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/sun/nio/cs/UnicodeEncoder.java#L81
>
>
>
>         jdk 17
>         Benchmark (data)  Mode  Cnt     Score     Error  Units
>         CharSequenceCharAtBenchmark.testString ascii  avgt    3
>          1429.358 ± 623.424  ns/op
>         CharSequenceCharAtBenchmark.testString non-ascii  avgt    3  
>         705.282 ± 233.453  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  ascii  avgt  
>          3   724.138 ± 267.346  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt
>            3   718.357 ± 864.066  ns/op
>
>         jdk 21
>         Benchmark (data)  Mode  Cnt     Score     Error  Units
>         CharSequenceCharAtBenchmark.testString ascii  avgt    3
>          1087.024 ┬▒ 235.082  ns/op
>         CharSequenceCharAtBenchmark.testString non-ascii  avgt    3  
>         687.520 ┬▒ 747.532  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  ascii  avgt  
>          3   672.802 ┬▒  29.740  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt
>            3   689.964 ┬▒ 791.175  ns/op
>
>         jdk 25
>         Benchmark (data)  Mode  Cnt     Score      Error  Units
>         CharSequenceCharAtBenchmark.testString ascii  avgt    3
>          1176.057 ┬▒ 1157.979  ns/op
>         CharSequenceCharAtBenchmark.testString non-ascii  avgt    3  
>         697.382 ┬▒  231.144  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  ascii  avgt  
>          3   692.970 ┬▒  105.112  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt
>            3   703.178 ┬▒  446.019  ns/op
>
>         jdk 26
>         Benchmark (data)  Mode  Cnt     Score     Error  Units
>         CharSequenceCharAtBenchmark.testString ascii  avgt    3
>          1132.971 ┬▒ 350.786  ns/op
>         CharSequenceCharAtBenchmark.testString non-ascii  avgt    3  
>         688.201 ┬▒ 175.797  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  ascii  avgt  
>          3   704.380 ┬▒ 101.763  ns/op
>         CharSequenceCharAtBenchmark.testStringBuilder  non-ascii  avgt
>            3   673.622 ┬▒  51.462  ns/op
>
>
>         @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;
>
>             private String string;
>
>             private StringBuilder stringBuilder;
>
>             @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);
>                 }
>
>                 string = sb.toString();
>                 stringBuilder = sb;
>             }
>
>             @Benchmark
>             public int testString() {
>                 String sequence = this.string;
>                 int sum = 0;
>                 for (int i=0, j=sequence.length(); i<j; ++i) {
>                     sum += sequence.charAt(i);
>                 }
>                 return sum;
>             }
>
>             @Benchmark
>             public int testStringBuilder() {
>                 StringBuilder sequence = this.stringBuilder;
>                 int sum = 0;
>                 for (int i=0, j=sequence.length(); i<j; ++i) {
>                     sum += sequence.charAt(i);
>                 }
>                 return sum;
>             }
>         }
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20250724/bface8fc/attachment-0001.htm>


More information about the core-libs-dev mailing list