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...@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.li...@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-r...@openjdk.org> on
behalf of Brett Okken <brett.okken...@gmail.com>
*Sent:* Monday, July 21, 2025 4:01 PM
*To:* Roger Riggs <roger.ri...@oracle.com>
*Cc:* core-libs-dev@openjdk.org <core-libs-dev@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;
}
}