Skip to content

Commit 9fc518f

Browse files
eirbjoSandhya Viswanathan
authored andcommitted
8303401: Add a Vector API equalsIgnoreCase micro benchmark
Reviewed-by: ecaspole, sviswanathan, psandoz
1 parent 05faa73 commit 9fc518f

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
*/
24+
25+
package org.openjdk.bench.jdk.incubator.vector;
26+
27+
import jdk.incubator.vector.ByteVector;
28+
import jdk.incubator.vector.VectorMask;
29+
import jdk.incubator.vector.VectorSpecies;
30+
import org.openjdk.jmh.Main;
31+
import org.openjdk.jmh.annotations.*;
32+
import org.openjdk.jmh.infra.Blackhole;
33+
34+
import java.io.IOException;
35+
import java.nio.charset.StandardCharsets;
36+
import java.util.concurrent.TimeUnit;
37+
38+
import static jdk.incubator.vector.VectorOperators.*;
39+
40+
/**
41+
* Exploration of vectorized latin1 equalsIgnoreCase taking advantage of the fact
42+
* that ASCII and Latin1 were designed to optimize case-twiddling operations.
43+
*/
44+
@BenchmarkMode(Mode.AverageTime)
45+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
46+
@State(Scope.Benchmark)
47+
@Warmup(iterations = 5, time = 1)
48+
@Measurement(iterations = 5, time = 1)
49+
@Fork(value = 3, jvmArgsPrepend = {"--add-modules=jdk.incubator.vector"})
50+
public class EqualsIgnoreCaseBenchmark {
51+
static final VectorSpecies<Byte> SPECIES = ByteVector.SPECIES_PREFERRED;
52+
private byte[] a;
53+
private byte[] b;
54+
private int len;
55+
@Param({"16", "32", "64", "128", "1024"})
56+
private int size;
57+
58+
@Setup
59+
public void setup() {
60+
a = ("a\u00e5".repeat(size/2) + "A").getBytes(StandardCharsets.ISO_8859_1);
61+
b = ("A\u00c5".repeat(size/2) + "B").getBytes(StandardCharsets.ISO_8859_1);
62+
len = a.length;
63+
}
64+
65+
@Benchmark
66+
public boolean scalar() {
67+
return scalarEqualsIgnoreCase(a, b, len);
68+
}
69+
70+
@Benchmark
71+
public boolean vectorized() {
72+
return vectorizedEqualsIgnoreCase(a, b, len);
73+
}
74+
75+
private boolean vectorizedEqualsIgnoreCase(byte[] a, byte[] b, int len) {
76+
int i = 0;
77+
for (; i < SPECIES.loopBound(b.length); i += SPECIES.length()) {
78+
ByteVector va = ByteVector.fromArray(SPECIES, a, i);
79+
ByteVector vb = ByteVector.fromArray(SPECIES, b, i);
80+
VectorMask<Byte> equal = va.eq(vb);
81+
82+
// If all bytes are equal, we can skip ahead early
83+
if (equal.allTrue()) {
84+
continue;
85+
}
86+
87+
// ASCII and Latin-1 were designed to optimize case-twiddling operations
88+
ByteVector upperA = va.and((byte) 0xDF);
89+
90+
// Determine which bytes represent ASCII or Latin-1 letters:
91+
VectorMask<Byte> asciiLetter = upperA.compare(GT, (byte) '@')
92+
.and(upperA.compare(LT, (byte) '['));
93+
94+
VectorMask<Byte> lat1Letter = upperA
95+
.compare(LT, (byte) 0xDF) // <= Thorn
96+
.and(upperA.compare(GT, (byte) 0XBF)) // >= A-grave
97+
.and(upperA.compare(EQ, (byte) 0xD7).not()); // Excluding multiplication
98+
99+
VectorMask<Byte> letter = asciiLetter.or(lat1Letter);
100+
101+
// Uppercase b
102+
ByteVector upperB = vb.and((byte) 0xDF);
103+
104+
// va equalsIgnoreCase vb if:
105+
// 1: all bytes are equal, or
106+
// 2: all bytes are letters in the ASCII or latin1 ranges
107+
// AND their uppercase is the same
108+
VectorMask<Byte> equalsIgnoreCase = equal
109+
.or(letter.and(upperA.eq(upperB)));
110+
111+
if (equalsIgnoreCase.allTrue()) {
112+
continue;
113+
} else {
114+
return false;
115+
}
116+
}
117+
// Process the tail
118+
while (i < len) {
119+
byte b1 = a[i];
120+
byte b2 = b[i];
121+
if (equalsIgnoreCase(b1, b2)) {
122+
i++;
123+
continue;
124+
}
125+
return false;
126+
}
127+
return true;
128+
}
129+
130+
public boolean scalarEqualsIgnoreCase(byte[] a, byte[] b, int len) {
131+
int i = 0;
132+
while (i < len) {
133+
byte b1 = a[i];
134+
byte b2 = b[i];
135+
if (equalsIgnoreCase(b1, b2)) {
136+
i++;
137+
continue;
138+
}
139+
return false;
140+
}
141+
return true;
142+
}
143+
144+
static boolean equalsIgnoreCase(byte b1, byte b2) {
145+
if (b1 == b2) {
146+
return true;
147+
}
148+
// ASCII and Latin-1 were designed to optimize case-twiddling operations
149+
int upper = b1 & 0xDF;
150+
if (upper < 'A') {
151+
return false; // Low ASCII
152+
}
153+
return (upper <= 'Z' // In range A-Z
154+
|| (upper >= 0xC0 && upper <= 0XDE && upper != 0xD7)) // ..or A-grave-Thorn, excl. multiplication
155+
&& upper == (b2 & 0xDF); // b2 has same uppercase
156+
}
157+
}

0 commit comments

Comments
 (0)