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