Skip to content

Commit 861e302

Browse files
committed
8302163: Speed up various String comparison methods with ArraysSupport.mismatch
Reviewed-by: stsypanov, rriggs, alanb
1 parent 50dcc2a commit 861e302

File tree

5 files changed

+127
-64
lines changed

5 files changed

+127
-64
lines changed

src/java.base/share/classes/java/lang/String.java

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.stream.Stream;
5252
import java.util.stream.StreamSupport;
5353

54+
import jdk.internal.util.ArraysSupport;
5455
import jdk.internal.util.Preconditions;
5556
import jdk.internal.vm.annotation.ForceInline;
5657
import jdk.internal.vm.annotation.IntrinsicCandidate;
@@ -1272,8 +1273,7 @@ private static void throwUnmappable(int off) {
12721273
}
12731274

12741275
private static void throwUnmappable(byte[] val) {
1275-
int dp = 0;
1276-
while (dp < val.length && val[dp] >=0) { dp++; }
1276+
int dp = StringCoding.countPositives(val, 0, val.length);
12771277
throwUnmappable(dp);
12781278
}
12791279

@@ -1870,23 +1870,17 @@ private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
18701870
if (len != sb.length()) {
18711871
return false;
18721872
}
1873-
byte v1[] = value;
1874-
byte v2[] = sb.getValue();
1873+
byte[] v1 = value;
1874+
byte[] v2 = sb.getValue();
18751875
byte coder = coder();
18761876
if (coder == sb.getCoder()) {
1877-
int n = v1.length;
1878-
for (int i = 0; i < n; i++) {
1879-
if (v1[i] != v2[i]) {
1880-
return false;
1881-
}
1882-
}
1877+
return v1.length <= v2.length && ArraysSupport.mismatch(v1, v2, v1.length) < 0;
18831878
} else {
18841879
if (coder != LATIN1) { // utf16 str and latin1 abs can never be "equal"
18851880
return false;
18861881
}
18871882
return StringUTF16.contentEquals(v1, v2, len);
18881883
}
1889-
return true;
18901884
}
18911885

18921886
/**
@@ -2024,8 +2018,8 @@ public boolean equalsIgnoreCase(String anotherString) {
20242018
* lexicographically greater than the string argument.
20252019
*/
20262020
public int compareTo(String anotherString) {
2027-
byte v1[] = value;
2028-
byte v2[] = anotherString.value;
2021+
byte[] v1 = value;
2022+
byte[] v2 = anotherString.value;
20292023
byte coder = coder();
20302024
if (coder == anotherString.coder()) {
20312025
return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
@@ -2060,8 +2054,8 @@ private static class CaseInsensitiveComparator
20602054
private static final long serialVersionUID = 8575799808933029326L;
20612055

20622056
public int compare(String s1, String s2) {
2063-
byte v1[] = s1.value;
2064-
byte v2[] = s2.value;
2057+
byte[] v1 = s1.value;
2058+
byte[] v2 = s2.value;
20652059
byte coder = s1.coder();
20662060
if (coder == s2.coder()) {
20672061
return coder == LATIN1 ? StringLatin1.compareToCI(v1, v2)
@@ -2136,26 +2130,23 @@ public int compareToIgnoreCase(String str) {
21362130
* {@code false} otherwise.
21372131
*/
21382132
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
2139-
byte tv[] = value;
2140-
byte ov[] = other.value;
21412133
// Note: toffset, ooffset, or len might be near -1>>>1.
21422134
if ((ooffset < 0) || (toffset < 0) ||
21432135
(toffset > (long)length() - len) ||
21442136
(ooffset > (long)other.length() - len)) {
21452137
return false;
21462138
}
2139+
byte[] tv = value;
2140+
byte[] ov = other.value;
21472141
byte coder = coder();
21482142
if (coder == other.coder()) {
2149-
if (!isLatin1() && (len > 0)) {
2150-
toffset = toffset << 1;
2151-
ooffset = ooffset << 1;
2152-
len = len << 1;
2153-
}
2154-
while (len-- > 0) {
2155-
if (tv[toffset++] != ov[ooffset++]) {
2156-
return false;
2157-
}
2143+
if (coder == UTF16) {
2144+
toffset <<= UTF16;
2145+
ooffset <<= UTF16;
2146+
len <<= UTF16;
21582147
}
2148+
return ArraysSupport.mismatch(tv, toffset,
2149+
ov, ooffset, len) < 0;
21592150
} else {
21602151
if (coder == LATIN1) {
21612152
while (len-- > 0) {
@@ -2235,8 +2226,8 @@ public boolean regionMatches(boolean ignoreCase, int toffset,
22352226
|| (ooffset > (long)other.length() - len)) {
22362227
return false;
22372228
}
2238-
byte tv[] = value;
2239-
byte ov[] = other.value;
2229+
byte[] tv = value;
2230+
byte[] ov = other.value;
22402231
byte coder = coder();
22412232
if (coder == other.coder()) {
22422233
return coder == LATIN1
@@ -2270,18 +2261,17 @@ public boolean startsWith(String prefix, int toffset) {
22702261
if (toffset < 0 || toffset > length() - prefix.length()) {
22712262
return false;
22722263
}
2273-
byte ta[] = value;
2274-
byte pa[] = prefix.value;
2264+
byte[] ta = value;
2265+
byte[] pa = prefix.value;
22752266
int po = 0;
22762267
int pc = pa.length;
22772268
byte coder = coder();
22782269
if (coder == prefix.coder()) {
2279-
int to = (coder == LATIN1) ? toffset : toffset << 1;
2280-
while (po < pc) {
2281-
if (ta[to++] != pa[po++]) {
2282-
return false;
2283-
}
2270+
if (coder == UTF16) {
2271+
toffset <<= UTF16;
22842272
}
2273+
return ArraysSupport.mismatch(ta, toffset,
2274+
pa, 0, pc) < 0;
22852275
} else {
22862276
if (coder == LATIN1) { // && pcoder == UTF16
22872277
return false;

src/java.base/share/classes/java/lang/StringLatin1.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,8 @@ public static int compareTo(byte[] value, byte[] other) {
109109

110110
public static int compareTo(byte[] value, byte[] other, int len1, int len2) {
111111
int lim = Math.min(len1, len2);
112-
for (int k = 0; k < lim; k++) {
113-
if (value[k] != other[k]) {
114-
return getChar(value, k) - getChar(other, k);
115-
}
116-
}
117-
return len1 - len2;
112+
int k = ArraysSupport.mismatch(value, other, lim);
113+
return (k < 0) ? len1 - len2 : getChar(value, k) - getChar(other, k);
118114
}
119115

120116
@IntrinsicCandidate

test/micro/org/openjdk/bench/java/lang/StringBuilders.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ public class StringBuilders {
5151
private String[] str3p9p8;
5252
private String[] str22p40p31;
5353
private StringBuilder sbLatin1;
54+
private StringBuilder sbLatin2;
5455
private StringBuilder sbUtf16;
56+
private StringBuilder sbUtf17;
5557

5658
@Setup
5759
public void setup() {
@@ -64,7 +66,9 @@ public void setup() {
6466
str3p9p8 = new String[]{"123", "123456789", "12345678"};
6567
str22p40p31 = new String[]{"1234567890123456789012", "1234567890123456789012345678901234567890", "1234567890123456789012345678901"};
6668
sbLatin1 = new StringBuilder("Latin1 string");
69+
sbLatin2 = new StringBuilder("Latin1 string");
6770
sbUtf16 = new StringBuilder("UTF-\uFF11\uFF16 string");
71+
sbUtf17 = new StringBuilder("UTF-\uFF11\uFF16 string");
6872
}
6973

7074
@Benchmark
@@ -250,6 +254,15 @@ public String toStringCharWithFloat8() {
250254
return result.toString();
251255
}
252256

257+
@Benchmark
258+
public int compareToLatin1() {
259+
return sbLatin1.compareTo(sbLatin2);
260+
}
261+
262+
@Benchmark
263+
public int compareToUTF16() {
264+
return sbUtf16.compareTo(sbUtf17);
265+
}
253266

254267
@Benchmark
255268
public String toStringCharWithMixed8() {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
package org.openjdk.bench.java.lang;
24+
25+
import org.openjdk.jmh.annotations.*;
26+
27+
import java.util.concurrent.TimeUnit;
28+
29+
/*
30+
* This benchmark naively explores String::startsWith and other String
31+
* comparison methods
32+
*/
33+
@BenchmarkMode(Mode.AverageTime)
34+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
35+
@State(Scope.Benchmark)
36+
@Warmup(iterations = 5, time = 1)
37+
@Measurement(iterations = 5, time = 1)
38+
@Fork(value = 3)
39+
public class StringComparisons {
40+
41+
@Param({"6", "15", "1024"})
42+
public int size;
43+
44+
@Param({"true", "false"})
45+
public boolean utf16;
46+
47+
public String string;
48+
public String equalString;
49+
public String endsWithA;
50+
public String endsWithB;
51+
public String startsWithA;
52+
53+
@Setup
54+
public void setup() {
55+
String c = utf16 ? "\uff11" : "c";
56+
string = c.repeat(size);
57+
equalString = c.repeat(size);
58+
endsWithA = c.repeat(size).concat("A");
59+
endsWithB = c.repeat(size).concat("B");
60+
startsWithA = "A" + (c.repeat(size));
61+
}
62+
63+
@Benchmark
64+
public boolean startsWith() {
65+
return endsWithA.startsWith(string);
66+
}
67+
68+
@Benchmark
69+
public boolean endsWith() {
70+
return startsWithA.endsWith(string);
71+
}
72+
73+
@Benchmark
74+
public boolean regionMatches() {
75+
return endsWithA.regionMatches(0, endsWithB, 0, endsWithB.length());
76+
}
77+
78+
@Benchmark
79+
public boolean regionMatchesRange() {
80+
return startsWithA.regionMatches(1, endsWithB, 0, endsWithB.length() - 1);
81+
}
82+
83+
@Benchmark
84+
public boolean regionMatchesCI() {
85+
return endsWithA.regionMatches(true, 0, endsWithB, 0, endsWithB.length());
86+
}
87+
}

test/micro/org/openjdk/bench/java/lang/StringOther.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,9 @@ public class StringOther {
4949
private String testString;
5050
private Random rnd;
5151

52-
private String str1, str2, str3, str4;
53-
private String str1UP;
54-
5552
@Setup
5653
public void setup() {
5754
testString = "Idealism is what precedes experience; cynicism is what follows.";
58-
str1 = "vm-guld vm-guld vm-guld";
59-
str1UP = str1.toUpperCase(Locale.ROOT);
60-
str2 = "vm-guld vm-guld vm-guldx";
61-
str3 = "vm-guld vm-guld vm-guldx";
62-
str4 = "adadaskasdjierudks";
6355
rnd = new Random();
6456
}
6557

@@ -70,15 +62,6 @@ public void charAt(Blackhole bh) {
7062
}
7163
}
7264

73-
@Benchmark
74-
public int compareTo() {
75-
int total = 0;
76-
total += str1.compareTo(str2);
77-
total += str2.compareTo(str3);
78-
total += str3.compareTo(str4);
79-
return total;
80-
}
81-
8265
/**
8366
* Creates (hopefully) unique Strings and internizes them, creating a zillion forgettable strings in the JVMs string
8467
* pool.
@@ -94,10 +77,4 @@ public String internUnique() {
9477
return String.valueOf(rnd.nextInt()).intern();
9578
}
9679

97-
@Benchmark
98-
public void regionMatchesLatin1(Blackhole bh) {
99-
bh.consume(str1.regionMatches(true, 0, str2, 0, str1.length()));
100-
bh.consume(str2.regionMatches(true, 16, str1UP, 0, 8));
101-
bh.consume(str3.regionMatches(true, 6, str4, 1, 2));
102-
}
10380
}

0 commit comments

Comments
 (0)