From 1edcd9048ae81e3270119761fcf0d32af94fdff8 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 3 Aug 2023 06:12:25 +0900 Subject: [PATCH 01/24] Apply SWAR to AsciiString Motivation: Currently, `AsciiString#indexOf` method using naive iteration algorithm. Modification: Utilize SWAR technique. Result: Faster `AsciiString#indexOf`. --- .../java/io/netty/buffer/ByteBufUtil.java | 16 +-- .../main/java/io/netty/util/AsciiString.java | 12 +- .../java/io/netty/util/AsciiStringUtil.java | 125 ++++++++++++++++++ .../netty/util/AsciiStringCharacterTest.java | 1 + .../common/AsciiStringIndexOfBenchmark.java | 99 ++++++++++++++ 5 files changed, 232 insertions(+), 21 deletions(-) create mode 100644 common/src/main/java/io/netty/util/AsciiStringUtil.java create mode 100644 microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index 3f0c5880bc9..f95b2dd740b 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -16,6 +16,7 @@ package io.netty.buffer; import io.netty.util.AsciiString; +import io.netty.util.AsciiStringUtil.SWARByteSearch; import io.netty.util.ByteProcessor; import io.netty.util.CharsetUtil; import io.netty.util.IllegalReferenceCountException; @@ -514,21 +515,6 @@ private static long uintFromLE(long value) { return Long.reverseBytes(value) >>> Integer.SIZE; } - private static final class SWARByteSearch { - - private static long compilePattern(byte byteToFind) { - return (byteToFind & 0xFFL) * 0x101010101010101L; - } - - private static int firstAnyPattern(long word, long pattern, boolean leading) { - long input = word ^ pattern; - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; - tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; - } - } - private static int unrolledFirstIndexOf(AbstractByteBuf buffer, int fromIndex, int byteCount, byte value) { assert byteCount > 0 && byteCount < 8; if (buffer._getByte(fromIndex) == value) { diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index d162526528b..8e8238d16f8 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -744,13 +744,13 @@ public int indexOf(char ch, int start) { } final byte chAsByte = c2b0(ch); - final int len = offset + length; - for (int i = start + offset; i < len; ++i) { - if (value[i] == chAsByte) { - return i - offset; - } + final int fromIndex = start + offset; + final int toIndex = offset + length; + final int index = AsciiStringUtil.firstIndexOf(value, fromIndex, toIndex, chAsByte); + if (index < 0) { + return INDEX_NOT_FOUND; } - return INDEX_NOT_FOUND; + return index - offset; } /** diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java new file mode 100644 index 00000000000..06a61f89fd1 --- /dev/null +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -0,0 +1,125 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.util; + +import io.netty.util.internal.PlatformDependent; + +/** + * A collection of utility methods that is related with handling {@link AsciiString} + */ +public final class AsciiStringUtil { + + public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + if (!PlatformDependent.isUnaligned()) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (bytes[idx] == value) { + return idx; + } + } + return -1; + } + + final int length = toIndex - fromIndex; + final int byteCount = length & 7; + if (byteCount > 0) { + final int index = unrolledFirstIndexOf(bytes, fromIndex, byteCount, value); + if (index >= 0) { + return index; + } + fromIndex += byteCount; + if (fromIndex == toIndex) { + return -1; + } + } + + final int longCount = length >>> 3; + final boolean isNative = PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; + final long pattern = SWARByteSearch.compilePattern(value); + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(bytes, fromIndex); + final int mask = SWARByteSearch.firstAnyPattern(word, pattern, isNative); + if (mask < Long.BYTES) { + return fromIndex + mask; + } + fromIndex += Long.BYTES; + } + return -1; + } + + public static final class SWARByteSearch { + public static long compilePattern(byte byteToFind) { + return (byteToFind & 0xFFL) * 0x101010101010101L; + } + + public static int firstAnyPattern(long word, long pattern, boolean leading) { + long input = word ^ pattern; + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; + tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); + return binaryPosition >>> 3; + } + + private SWARByteSearch() { + } + } + + private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int length, byte target) { + assert length > 0 && length < 8; + if (bytes[fromIndex] == target) { + return fromIndex; + } + if (length == 1) { + return -1; + } + if (bytes[fromIndex + 1] == target) { + return fromIndex + 1; + } + if (length == 2) { + return -1; + } + if (bytes[fromIndex + 2] == target) { + return fromIndex + 2; + } + if (length == 3) { + return -1; + } + if (bytes[fromIndex + 3] == target) { + return fromIndex + 3; + } + if (length == 4) { + return -1; + } + if (bytes[fromIndex + 4] == target) { + return fromIndex + 4; + } + if (length == 5) { + return -1; + } + if (bytes[fromIndex + 5] == target) { + return fromIndex + 5; + } + if (length == 6) { + return -1; + } + if (bytes[fromIndex + 6] == target) { + return fromIndex + 6; + } + return -1; + } + + private AsciiStringUtil() { + } + +} diff --git a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java index b2a9214e7ab..4e0686a44cd 100644 --- a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java @@ -313,6 +313,7 @@ public void testIndexOfChar() { assertEquals(1, AsciiString.of("aabaabaa").indexOf('a', 1)); assertEquals(3, AsciiString.of("aabaabaa").indexOf('a', 2)); assertEquals(3, AsciiString.of("aabdabaa").indexOf('d', 1)); + assertEquals(15, AsciiString.of("abcdefghijklmnop").indexOf('p', 0)); assertEquals(1, new AsciiString("abcd", 1, 2).indexOf('c', 0)); assertEquals(2, new AsciiString("abcd", 1, 3).indexOf('d', 2)); assertEquals(0, new AsciiString("abcd", 1, 2).indexOf('b', 0)); diff --git a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java new file mode 100644 index 00000000000..f84ccdda742 --- /dev/null +++ b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.microbenchmark.common; + +import io.netty.microbench.util.AbstractMicrobenchmark; +import io.netty.util.AsciiString; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SuppressJava6Requirement; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.SplittableRandom; +import java.util.concurrent.TimeUnit; + +@Threads(1) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(2) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 8, time = 1) +@State(Scope.Benchmark) +public class AsciiStringIndexOfBenchmark extends AbstractMicrobenchmark { + public static Object blackhole; + + @Param({ "7", "11", "23", "32" }) + int size; + @Param({ "11" }) + int logPermutations; + + @Param({ "1" }) + int seed; + + int permutations; + AsciiString[] data; + private int i; + + @Param({ "0" }) + private byte needleByte; + + @Param({ "true", "false" }) + private boolean noUnsafe; + + @Setup(Level.Trial) + @SuppressJava6Requirement(reason = "using SplittableRandom to reliably produce data") + public void init() { + System.setProperty("io.netty.noUnsafe", Boolean.valueOf(noUnsafe).toString()); + blackhole = PlatformDependent.isUnaligned(); + SplittableRandom random = new SplittableRandom(seed); + permutations = 1 << logPermutations; + data = new AsciiString[permutations]; + for (int i = 0; i < permutations; ++i) { + byte[] byteArray = new byte[size]; + for (int j = 0; j < size; j++) { + int value = random.nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE + 1); + // turn any found value into something different + if (value == needleByte) { + if (needleByte != 1) { + value = 1; + } else { + value = 0; + } + } + byteArray[j] = (byte) value; + } + final int foundIndex = random.nextInt(Math.max(0, size - 8), size); + byteArray[foundIndex] = needleByte; + data[i] = new AsciiString(byteArray); + } + } + + private AsciiString getData() { + return data[i++ & permutations - 1]; + } + + @Benchmark + public int indexOf() { + return getData().indexOf((char) needleByte, 0); + } +} From 1d48e0578f4739d4c5e82adeed65194b4d047839 Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 7 Aug 2023 07:20:56 +0900 Subject: [PATCH 02/24] Apply SWAR to case conversion --- .../java/io/netty/buffer/ByteBufUtil.java | 6 +- .../main/java/io/netty/util/AsciiString.java | 39 +-- .../java/io/netty/util/AsciiStringUtil.java | 261 ++++++++++++++---- .../netty/util/AsciiStringCharacterTest.java | 12 + .../AsciiStringCaseConversionBenchmark.java | 94 +++++++ 5 files changed, 323 insertions(+), 89 deletions(-) create mode 100644 microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index f95b2dd740b..682201e5115 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -16,7 +16,7 @@ package io.netty.buffer; import io.netty.util.AsciiString; -import io.netty.util.AsciiStringUtil.SWARByteSearch; +import io.netty.util.AsciiStringUtil.SWARByteUtil; import io.netty.util.ByteProcessor; import io.netty.util.CharsetUtil; import io.netty.util.IllegalReferenceCountException; @@ -590,11 +590,11 @@ static int firstIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte final ByteOrder nativeOrder = ByteOrder.nativeOrder(); final boolean isNative = nativeOrder == buffer.order(); final boolean useLE = nativeOrder == ByteOrder.LITTLE_ENDIAN; - final long pattern = SWARByteSearch.compilePattern(value); + final long pattern = SWARByteUtil.compilePattern(value); for (int i = 0; i < longCount; i++) { // use the faster available getLong final long word = useLE? buffer._getLongLE(offset) : buffer._getLong(offset); - int index = SWARByteSearch.firstAnyPattern(word, pattern, isNative); + int index = SWARByteUtil.firstAnyPattern(word, pattern, isNative); if (index < Long.BYTES) { return offset + index; } diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 8e8238d16f8..d75fc91ec55 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -941,27 +941,12 @@ public boolean startsWith(CharSequence prefix, int start) { * @return a new string containing the lowercase characters equivalent to the characters in this string. */ public AsciiString toLowerCase() { - boolean lowercased = true; - int i, j; - final int len = length() + arrayOffset(); - for (i = arrayOffset(); i < len; ++i) { - byte b = value[i]; - if (b >= 'A' && b <= 'Z') { - lowercased = false; - break; - } - } - - // Check if this string does not contain any uppercase characters. - if (lowercased) { + if (!AsciiStringUtil.containsUpperCase(value, offset,offset + length)) { return this; } final byte[] newValue = PlatformDependent.allocateUninitializedArray(length()); - for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) { - newValue[i] = toLowerCase(value[j]); - } - + AsciiStringUtil.toLowerCase(value, offset, newValue, 0, length()); return new AsciiString(newValue, false); } @@ -971,27 +956,11 @@ public AsciiString toLowerCase() { * @return a new string containing the uppercase characters equivalent to the characters in this string. */ public AsciiString toUpperCase() { - boolean uppercased = true; - int i, j; - final int len = length() + arrayOffset(); - for (i = arrayOffset(); i < len; ++i) { - byte b = value[i]; - if (b >= 'a' && b <= 'z') { - uppercased = false; - break; - } - } - - // Check if this string does not contain any lowercase characters. - if (uppercased) { + if (!AsciiStringUtil.containsLowerCase(value, offset, offset + length)) { return this; } - final byte[] newValue = PlatformDependent.allocateUninitializedArray(length()); - for (i = 0, j = arrayOffset(); i < newValue.length; ++i, ++j) { - newValue[i] = toUpperCase(value[j]); - } - + AsciiStringUtil.toUpperCase(value, offset, newValue, 0, length()); return new AsciiString(newValue, false); } diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 06a61f89fd1..4319f1b2f3e 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -21,6 +21,54 @@ */ public final class AsciiStringUtil { + private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int length, byte target) { + assert length >= 0 && length < 8; + if (length == 0) { + return -1; + } + + if (bytes[fromIndex] == target) { + return fromIndex; + } + if (length == 1) { + return -1; + } + if (bytes[fromIndex + 1] == target) { + return fromIndex + 1; + } + if (length == 2) { + return -1; + } + if (bytes[fromIndex + 2] == target) { + return fromIndex + 2; + } + if (length == 3) { + return -1; + } + if (bytes[fromIndex + 3] == target) { + return fromIndex + 3; + } + if (length == 4) { + return -1; + } + if (bytes[fromIndex + 4] == target) { + return fromIndex + 4; + } + if (length == 5) { + return -1; + } + if (bytes[fromIndex + 5] == target) { + return fromIndex + 5; + } + if (length == 6) { + return -1; + } + if (bytes[fromIndex + 6] == target) { + return fromIndex + 6; + } + return -1; + } + public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { if (!PlatformDependent.isUnaligned()) { for (int idx = fromIndex; idx < toIndex; ++idx) { @@ -32,37 +80,49 @@ public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte va } final int length = toIndex - fromIndex; - final int byteCount = length & 7; - if (byteCount > 0) { - final int index = unrolledFirstIndexOf(bytes, fromIndex, byteCount, value); - if (index >= 0) { - return index; - } - fromIndex += byteCount; - if (fromIndex == toIndex) { - return -1; - } - } final int longCount = length >>> 3; - final boolean isNative = PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; - final long pattern = SWARByteSearch.compilePattern(value); - for (int i = 0; i < longCount; ++i) { - final long word = PlatformDependent.getLong(bytes, fromIndex); - final int mask = SWARByteSearch.firstAnyPattern(word, pattern, isNative); - if (mask < Long.BYTES) { - return fromIndex + mask; + if (longCount > 0) { + final boolean isNative = PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; + final long pattern = SWARByteUtil.compilePattern(value); + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(bytes, fromIndex); + final int mask = SWARByteUtil.firstAnyPattern(word, pattern, isNative); + if (mask < Long.BYTES) { + return fromIndex + mask; + } + fromIndex += Long.BYTES; } - fromIndex += Long.BYTES; } - return -1; + + return unrolledFirstIndexOf(bytes, fromIndex, toIndex - fromIndex, value); } - public static final class SWARByteSearch { + + public static final class SWARByteUtil { + static final long UPPER_CASE_PATTERN = compilePattern((byte) 'A'); + static final long UPPER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'A', (byte) 'Z'); + + static final long LOWER_CASE_PATTERN = compilePattern((byte) 'a'); + static final long LOWER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'a', (byte) 'z'); + public static long compilePattern(byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } + private static long compileRangePattern(byte low, byte high) { + assert low <= high && high - low <= 128; + return (0x7F7F - high + low & 0xFFL) * 0x101010101010101L; + } + + private static long applyPatternRange(long word, long lowPattern, long rangePattern) { + long input = (word | 0x8080808080808080L) - lowPattern; + input = ~((word | 0x7F7F7F7F7F7F7F7FL) ^ input); + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + rangePattern; + return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + } + + public static int firstAnyPattern(long word, long pattern, boolean leading) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; @@ -71,54 +131,153 @@ public static int firstAnyPattern(long word, long pattern, boolean leading) { return binaryPosition >>> 3; } - private SWARByteSearch() { + private SWARByteUtil() { } } - private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int length, byte target) { - assert length > 0 && length < 8; - if (bytes[fromIndex] == target) { - return fromIndex; + static boolean isLowerCase(byte value) { + return value >= 'a' && value <= 'z'; + } + + static boolean isUpperCase(byte value) { + return value >= 'A' && value <= 'Z'; + } + + private static long toLowerCase(long word) { + long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, + SWARByteUtil.UPPER_CASE_RANGE_PATTERN); + return word | mask >>> 2; + } + + private static long toUpperCase(long word) { + long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, + SWARByteUtil.LOWER_CASE_RANGE_PATTERN); + return word & ~(mask >>> 2); + } + + static byte toUpperCase(byte value) { + if (isLowerCase(value)) { + return (byte)(value & ~32); } - if (length == 1) { - return -1; + return value; + } + + static byte toLowerCase(byte value) { + if (isUpperCase(value)) { + return (byte) (value | 32); } - if (bytes[fromIndex + 1] == target) { - return fromIndex + 1; + return value; + } + + + static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { + if (!PlatformDependent.isUnaligned()) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (isUpperCase(bytes[idx])) { + return true; + } + } + return false; } - if (length == 2) { - return -1; + + final int length = toIndex - fromIndex; + final int longCount = length >>> 3; + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(bytes, fromIndex); + final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, + SWARByteUtil.UPPER_CASE_RANGE_PATTERN); + if (mask != 0) { + return true; + } + fromIndex += Long.BYTES; } - if (bytes[fromIndex + 2] == target) { - return fromIndex + 2; + + for (; fromIndex < toIndex; ++fromIndex) { + byte value = bytes[fromIndex]; + if (isUpperCase(value)) { + return true; + } } - if (length == 3) { - return -1; + return false; + } + + static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { + if (!PlatformDependent.isUnaligned()) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (isLowerCase(bytes[idx])) { + return true; + } + } + return false; } - if (bytes[fromIndex + 3] == target) { - return fromIndex + 3; + + final int length = toIndex - fromIndex; + final int longCount = length >>> 3; + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(bytes, fromIndex); + final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, + SWARByteUtil.LOWER_CASE_RANGE_PATTERN); + if (mask != 0) { + return true; + } + fromIndex += Long.BYTES; } - if (length == 4) { - return -1; + + for (; fromIndex < toIndex; ++fromIndex) { + if (isLowerCase(bytes[fromIndex])) { + return true; + } } - if (bytes[fromIndex + 4] == target) { - return fromIndex + 4; + return false; + } + + + static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + if (!PlatformDependent.isUnaligned()) { + for (int i = 0; i < length; ++i) { + dest[destPos++] = toLowerCase(src[srcPos++]); + } + return; } - if (length == 5) { - return -1; + + final int longCount = length >>> 3; + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(src, srcPos); + PlatformDependent.putLong(dest, destPos, toLowerCase(word)); + srcPos += Long.BYTES; + destPos += Long.BYTES; } - if (bytes[fromIndex + 5] == target) { - return fromIndex + 5; + + final int byteCount = length & 7; + for (int i = 0; i < byteCount; ++i) { + dest[destPos++] = toLowerCase(src[srcPos++]); } - if (length == 6) { - return -1; + } + + static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + if (!PlatformDependent.isUnaligned()) { + for (int i = 0; i < length; ++i) { + dest[destPos++] = toUpperCase(src[srcPos++]); + } + return; } - if (bytes[fromIndex + 6] == target) { - return fromIndex + 6; + + final int longCount = length >>> 3; + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(src, srcPos); + PlatformDependent.putLong(dest, destPos, toUpperCase(word)); + srcPos += Long.BYTES; + destPos += Long.BYTES; + } + + final int byteCount = length & 7; + for (int i = 0; i < byteCount; ++i) { + dest[destPos++] = toUpperCase(src[srcPos++]); } - return -1; } + + private AsciiStringUtil() { } diff --git a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java index 4e0686a44cd..b3e790451c4 100644 --- a/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringCharacterTest.java @@ -434,4 +434,16 @@ public void testIndexOf() { int i4 = foo.indexOf(' ', i3 + 1); assertEquals(i4, -1); } + + @Test + public void testToLowerCase() { + AsciiString foo = AsciiString.of("This is a tesT"); + assertEquals("this is a test", foo.toLowerCase().toString()); + } + + @Test + public void testToUpperCase() { + AsciiString foo = AsciiString.of("This is a tesT"); + assertEquals("THIS IS A TEST", foo.toUpperCase().toString()); + } } diff --git a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java new file mode 100644 index 00000000000..278aec6076c --- /dev/null +++ b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.microbenchmark.common; + +import io.netty.microbench.util.AbstractMicrobenchmark; +import io.netty.util.AsciiString; +import io.netty.util.internal.SuppressJava6Requirement; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.SplittableRandom; +import java.util.concurrent.TimeUnit; + +@Threads(1) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(2) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 8, time = 1) +@State(Scope.Benchmark) +public class AsciiStringCaseConversionBenchmark extends AbstractMicrobenchmark { + + @Param({ "7", "16", "23", "32", "63" }) + int size; + @Param({ "11" }) + int logPermutations; + + @Param({ "1" }) + int seed; + + int permutations; // uniquenessness + AsciiString[] upperCaseData; + private int i; + + @Param({ "true", "false" }) + private boolean noUnsafe; + + @Setup(Level.Trial) + @SuppressJava6Requirement(reason = "using SplittableRandom to reliably produce data") + public void init() { + System.setProperty("io.netty.noUnsafe", Boolean.valueOf(noUnsafe).toString()); + SplittableRandom random = new SplittableRandom(seed); + permutations = 1 << logPermutations; + upperCaseData = new AsciiString[permutations]; + for (int i = 0; i < permutations; ++i) { + final int foundIndex = random.nextInt(Math.max(0, Math.min(size - 8, size >> 1)), size); + byte[] byteArray = new byte[size]; + int j = 0; + for (; j < size; j++) { + int value = random.nextInt(0, Byte.MAX_VALUE + 1); + // turn any found value into something different + if (j < foundIndex) { + if (value >= 'A' && value <= 'Z') { + value = Character.toLowerCase(value); + } + } + if (j == foundIndex) { + value = 'G'; + } + byteArray[j] = (byte) value; + } + upperCaseData[i] = new AsciiString(byteArray); + } + } + + private AsciiString getData() { + return upperCaseData[i++ & permutations - 1]; + } + + @Benchmark + public AsciiString toLowerCase() { + return getData().toLowerCase(); + } +} \ No newline at end of file From 88f3e9d2e2483be4ec69282fc5ed4e3db40cd595 Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 7 Aug 2023 07:22:39 +0900 Subject: [PATCH 03/24] Apply SWAR to case conversion --- common/src/main/java/io/netty/util/AsciiString.java | 2 +- common/src/main/java/io/netty/util/AsciiStringUtil.java | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index d75fc91ec55..ca71fbc815e 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -941,7 +941,7 @@ public boolean startsWith(CharSequence prefix, int start) { * @return a new string containing the lowercase characters equivalent to the characters in this string. */ public AsciiString toLowerCase() { - if (!AsciiStringUtil.containsUpperCase(value, offset,offset + length)) { + if (!AsciiStringUtil.containsUpperCase(value, offset, offset + length)) { return this; } diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 4319f1b2f3e..3eabd1c689d 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -98,7 +98,6 @@ public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte va return unrolledFirstIndexOf(bytes, fromIndex, toIndex - fromIndex, value); } - public static final class SWARByteUtil { static final long UPPER_CASE_PATTERN = compilePattern((byte) 'A'); static final long UPPER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'A', (byte) 'Z'); @@ -122,7 +121,6 @@ private static long applyPatternRange(long word, long lowPattern, long rangePatt return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); } - public static int firstAnyPattern(long word, long pattern, boolean leading) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; @@ -157,7 +155,7 @@ private static long toUpperCase(long word) { static byte toUpperCase(byte value) { if (isLowerCase(value)) { - return (byte)(value & ~32); + return (byte) (value & ~32); } return value; } @@ -169,7 +167,6 @@ static byte toLowerCase(byte value) { return value; } - static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { if (!PlatformDependent.isUnaligned()) { for (int idx = fromIndex; idx < toIndex; ++idx) { @@ -231,7 +228,6 @@ static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { return false; } - static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { if (!PlatformDependent.isUnaligned()) { for (int i = 0; i < length; ++i) { @@ -276,8 +272,6 @@ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int le } } - - private AsciiStringUtil() { } From 9fd80db9c9c1245b7ac91908cd36257cfc1fdff0 Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 7 Aug 2023 09:01:14 +0900 Subject: [PATCH 04/24] Apply EqualsIgnoreCase --- .../main/java/io/netty/util/AsciiString.java | 41 +------- .../java/io/netty/util/AsciiStringUtil.java | 97 ++++++++++++------- 2 files changed, 65 insertions(+), 73 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index ca71fbc815e..91c9f299946 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -534,18 +534,8 @@ public boolean contentEqualsIgnoreCase(CharSequence string) { } if (string instanceof AsciiString) { - AsciiString other = (AsciiString) string; - byte[] value = this.value; - if (offset == 0 && other.offset == 0 && length == value.length) { - byte[] otherValue = other.value; - for (int i = 0; i < value.length; ++i) { - if (!equalsIgnoreCase(value[i], otherValue[i])) { - return false; - } - } - return true; - } - return misalignedEqualsIgnoreCase(other); + AsciiString rhs = (AsciiString) string; + return AsciiStringUtil.equalsIgnoreCases(value, offset, rhs.value, rhs.offset, length()); } byte[] value = this.value; @@ -557,17 +547,6 @@ public boolean contentEqualsIgnoreCase(CharSequence string) { return true; } - private boolean misalignedEqualsIgnoreCase(AsciiString other) { - byte[] value = this.value; - byte[] otherValue = other.value; - for (int i = offset, j = other.offset, end = offset + length; i < end; ++i, ++j) { - if (!equalsIgnoreCase(value[i], otherValue[j])) { - return false; - } - } - return true; - } - /** * Copies the characters in this string to a character array. * @@ -1803,18 +1782,10 @@ public static int indexOf(final CharSequence cs, final char searchChar, int star return INDEX_NOT_FOUND; } - private static boolean equalsIgnoreCase(byte a, byte b) { - return a == b || toLowerCase(a) == toLowerCase(b); - } - private static boolean equalsIgnoreCase(char a, char b) { return a == b || toLowerCase(a) == toLowerCase(b); } - private static byte toLowerCase(byte b) { - return isUpperCase(b) ? (byte) (b + 32) : b; - } - /** * If the character is uppercase - converts the character to lowercase, * otherwise returns the character as it is. Only for ASCII characters. @@ -1825,14 +1796,6 @@ public static char toLowerCase(char c) { return isUpperCase(c) ? (char) (c + 32) : c; } - private static byte toUpperCase(byte b) { - return isLowerCase(b) ? (byte) (b - 32) : b; - } - - private static boolean isLowerCase(byte value) { - return value >= 'a' && value <= 'z'; - } - public static boolean isUpperCase(byte value) { return value >= 'A' && value <= 'Z'; } diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 3eabd1c689d..10b0c9a3435 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -98,40 +98,6 @@ public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte va return unrolledFirstIndexOf(bytes, fromIndex, toIndex - fromIndex, value); } - public static final class SWARByteUtil { - static final long UPPER_CASE_PATTERN = compilePattern((byte) 'A'); - static final long UPPER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'A', (byte) 'Z'); - - static final long LOWER_CASE_PATTERN = compilePattern((byte) 'a'); - static final long LOWER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'a', (byte) 'z'); - - public static long compilePattern(byte byteToFind) { - return (byteToFind & 0xFFL) * 0x101010101010101L; - } - - private static long compileRangePattern(byte low, byte high) { - assert low <= high && high - low <= 128; - return (0x7F7F - high + low & 0xFFL) * 0x101010101010101L; - } - - private static long applyPatternRange(long word, long lowPattern, long rangePattern) { - long input = (word | 0x8080808080808080L) - lowPattern; - input = ~((word | 0x7F7F7F7F7F7F7F7FL) ^ input); - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + rangePattern; - return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - } - - public static int firstAnyPattern(long word, long pattern, boolean leading) { - long input = word ^ pattern; - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; - tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; - } - - private SWARByteUtil() { - } - } static boolean isLowerCase(byte value) { return value >= 'a' && value <= 'z'; @@ -272,6 +238,69 @@ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int le } } + static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + if (lhs == rhs && lhsPos == rhsPos && lhs.length >= lhsPos + length) { + return true; + } + + int longCount = length >>> 3; + if (longCount > 0) { + for (int i = 0; i < longCount; ++i) { + final long lWord = PlatformDependent.getLong(lhs, lhsPos); + final long rWord = PlatformDependent.getLong(rhs, rhsPos); + if (toLowerCase(lWord) != toLowerCase(rWord)) { + return false; + } + lhsPos += Long.BYTES; + rhsPos += Long.BYTES; + } + } + int byteCount = length & 7; + if (byteCount > 0) { + for (int i = 0; i < byteCount; ++i) { + if (toLowerCase(lhs[lhsPos++]) != toLowerCase(rhs[rhsPos++])) { + return false; + } + } + } + return true; + } + + public static final class SWARByteUtil { + static final long UPPER_CASE_PATTERN = compilePattern((byte) 'A'); + static final long UPPER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'A', (byte) 'Z'); + + static final long LOWER_CASE_PATTERN = compilePattern((byte) 'a'); + static final long LOWER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'a', (byte) 'z'); + + public static long compilePattern(byte byteToFind) { + return (byteToFind & 0xFFL) * 0x101010101010101L; + } + + private static long compileRangePattern(byte low, byte high) { + assert low <= high && high - low <= 128; + return (0x7F7F - high + low & 0xFFL) * 0x101010101010101L; + } + + private static long applyPatternRange(long word, long lowPattern, long rangePattern) { + long input = (word | 0x8080808080808080L) - lowPattern; + input = ~((word | 0x7F7F7F7F7F7F7F7FL) ^ input); + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + rangePattern; + return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + } + + public static int firstAnyPattern(long word, long pattern, boolean leading) { + long input = word ^ pattern; + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; + tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); + return binaryPosition >>> 3; + } + + private SWARByteUtil() { + } + } + private AsciiStringUtil() { } From 476076611403b4f2f42615dacc84e0743842b32f Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 7 Aug 2023 09:10:25 +0900 Subject: [PATCH 05/24] remove unused --- common/src/main/java/io/netty/util/AsciiString.java | 2 +- .../src/main/java/io/netty/util/AsciiStringUtil.java | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 91c9f299946..7053068848b 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -1797,7 +1797,7 @@ public static char toLowerCase(char c) { } public static boolean isUpperCase(byte value) { - return value >= 'A' && value <= 'Z'; + return AsciiStringUtil.isUpperCase(value); } public static boolean isUpperCase(char value) { diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 10b0c9a3435..f9fa5634479 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -69,7 +69,7 @@ private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int length, return -1; } - public static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { if (!PlatformDependent.isUnaligned()) { for (int idx = fromIndex; idx < toIndex; ++idx) { if (bytes[idx] == value) { @@ -120,17 +120,11 @@ private static long toUpperCase(long word) { } static byte toUpperCase(byte value) { - if (isLowerCase(value)) { - return (byte) (value & ~32); - } - return value; + return isLowerCase(value)? (byte) (value & ~32) : value; } static byte toLowerCase(byte value) { - if (isUpperCase(value)) { - return (byte) (value | 32); - } - return value; + return isUpperCase(value)? (byte) (value | 32) : value; } static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { From a1b6ad9f0cd48797d43a9f50a1727f51b415f612 Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 8 Aug 2023 22:32:25 +0900 Subject: [PATCH 06/24] Resolve Checkstyle error --- common/src/main/java/io/netty/util/AsciiStringUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index f9fa5634479..1089dbc58cc 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -98,7 +98,6 @@ static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { return unrolledFirstIndexOf(bytes, fromIndex, toIndex - fromIndex, value); } - static boolean isLowerCase(byte value) { return value >= 'a' && value <= 'z'; } From 2c651f837c701bef8f88f1f963a27cd54d82c576 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 10 Aug 2023 20:53:23 +0900 Subject: [PATCH 07/24] avoid boundcheck --- .../java/io/netty/util/AsciiStringUtil.java | 109 ++++++++++-------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 1089dbc58cc..8aa63428d99 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -21,83 +21,81 @@ */ public final class AsciiStringUtil { - private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int length, byte target) { - assert length >= 0 && length < 8; - if (length == 0) { - return -1; + static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + if (!PlatformDependent.isUnaligned()) { + return linearFirstIndexOf(bytes, fromIndex, toIndex, value); } + final int length = toIndex - fromIndex; + final int longCount = length >>> 3; + final long pattern = SWARByteUtil.compilePattern(value); + for (int i = 0; i < longCount; ++i) { + final long word = PlatformDependent.getLong(bytes, fromIndex); + final long mask = SWARByteUtil.applyPattern(word, pattern); + if (mask != 0) { + return fromIndex + SWARByteUtil.getIndex(mask, PlatformDependent.BIG_ENDIAN_NATIVE_ORDER); + } + fromIndex += Long.BYTES; + } + final int byteCount = length & 7; + return unrolledFirstIndexOf(bytes, fromIndex, byteCount, value); + } - if (bytes[fromIndex] == target) { + private static int linearFirstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (bytes[idx] == value) { + return idx; + } + } + return -1; + } + + private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int byteCount, byte value) { + assert byteCount >= 0 && byteCount < 8; + if (byteCount == 0) { + return -1; + } + if (PlatformDependent.getByte(bytes, fromIndex) == value) { return fromIndex; } - if (length == 1) { + if (byteCount == 1) { return -1; } - if (bytes[fromIndex + 1] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 1) == value) { return fromIndex + 1; } - if (length == 2) { + if (byteCount == 2) { return -1; } - if (bytes[fromIndex + 2] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 2) == value) { return fromIndex + 2; } - if (length == 3) { + if (byteCount == 3) { return -1; } - if (bytes[fromIndex + 3] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 3) == value) { return fromIndex + 3; } - if (length == 4) { + if (byteCount == 4) { return -1; } - if (bytes[fromIndex + 4] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 4) == value) { return fromIndex + 4; } - if (length == 5) { + if (byteCount == 5) { return -1; } - if (bytes[fromIndex + 5] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 5) == value) { return fromIndex + 5; } - if (length == 6) { + if (byteCount == 6) { return -1; } - if (bytes[fromIndex + 6] == target) { + if (PlatformDependent.getByte(bytes, fromIndex + 6) == value) { return fromIndex + 6; } return -1; } - static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { - if (!PlatformDependent.isUnaligned()) { - for (int idx = fromIndex; idx < toIndex; ++idx) { - if (bytes[idx] == value) { - return idx; - } - } - return -1; - } - - final int length = toIndex - fromIndex; - - final int longCount = length >>> 3; - if (longCount > 0) { - final boolean isNative = PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; - final long pattern = SWARByteUtil.compilePattern(value); - for (int i = 0; i < longCount; ++i) { - final long word = PlatformDependent.getLong(bytes, fromIndex); - final int mask = SWARByteUtil.firstAnyPattern(word, pattern, isNative); - if (mask < Long.BYTES) { - return fromIndex + mask; - } - fromIndex += Long.BYTES; - } - } - - return unrolledFirstIndexOf(bytes, fromIndex, toIndex - fromIndex, value); - } - static boolean isLowerCase(byte value) { return value >= 'a' && value <= 'z'; } @@ -282,6 +280,16 @@ private static long applyPatternRange(long word, long lowPattern, long rangePatt return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); } + private static long applyPattern(long word, long pattern) { + long input = word ^ pattern; + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; + return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + } + + public static int getIndex(long mask, boolean isBigEndian) { + return isBigEndian? Long.numberOfLeadingZeros(mask) >>> 3 : Long.numberOfTrailingZeros(mask) >>> 3; + } + public static int firstAnyPattern(long word, long pattern, boolean leading) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; @@ -290,8 +298,17 @@ public static int firstAnyPattern(long word, long pattern, boolean leading) { return binaryPosition >>> 3; } + public static int firstAnyPatternInt(int word, int pattern, boolean leading) { + int input = word ^ pattern; + int tmp = (input & 0x7F7F7F7F) + 0x7F7F7F7F; + tmp = ~(tmp | input | 0x7F7F7F7F); + final int binaryPosition = leading? Integer.numberOfLeadingZeros(tmp) : Integer.numberOfTrailingZeros(tmp); + return binaryPosition >>> 3; + } + private SWARByteUtil() { } + } private AsciiStringUtil() { From 7cf37e4a1e36d2603572fb82a24ef0ae4a7d46e2 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 13 Aug 2023 23:07:19 +0900 Subject: [PATCH 08/24] Remove unused line --- common/src/main/java/io/netty/util/AsciiStringUtil.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 8aa63428d99..322122b5b57 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -298,17 +298,8 @@ public static int firstAnyPattern(long word, long pattern, boolean leading) { return binaryPosition >>> 3; } - public static int firstAnyPatternInt(int word, int pattern, boolean leading) { - int input = word ^ pattern; - int tmp = (input & 0x7F7F7F7F) + 0x7F7F7F7F; - tmp = ~(tmp | input | 0x7F7F7F7F); - final int binaryPosition = leading? Integer.numberOfLeadingZeros(tmp) : Integer.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; - } - private SWARByteUtil() { } - } private AsciiStringUtil() { From d82026fb4867927958a8ae7497daf6e239cff7ba Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 13 Aug 2023 23:19:55 +0900 Subject: [PATCH 09/24] ADD stringIndexOf benchmark --- .../common/AsciiStringIndexOfBenchmark.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java index f84ccdda742..84995b32616 100644 --- a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java +++ b/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java @@ -40,9 +40,9 @@ @Measurement(iterations = 8, time = 1) @State(Scope.Benchmark) public class AsciiStringIndexOfBenchmark extends AbstractMicrobenchmark { - public static Object blackhole; - @Param({ "7", "11", "23", "32" }) + public static Object blackhole; + @Param({ "7", "16", "23", "32" }) int size; @Param({ "11" }) int logPermutations; @@ -85,6 +85,7 @@ public void init() { final int foundIndex = random.nextInt(Math.max(0, size - 8), size); byteArray[foundIndex] = needleByte; data[i] = new AsciiString(byteArray); + blackhole = data[i].toString(); // cache } } @@ -96,4 +97,9 @@ private AsciiString getData() { public int indexOf() { return getData().indexOf((char) needleByte, 0); } + + @Benchmark + public int stringIndexOf() { + return getData().toString().indexOf((char) needleByte); + } } From 638cee008699e1558abdef923965715d5fc367af Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 13 Aug 2023 23:57:12 +0900 Subject: [PATCH 10/24] ADD toLowercase, containsUpperCase benchmark --- .../java/io/netty/util/AsciiStringUtil.java | 151 +++++++++++++++--- ...ciiStringUtilCaseConversionBenchmark.java} | 36 +++-- 2 files changed, 154 insertions(+), 33 deletions(-) rename microbench/src/main/java/io/netty/{microbenchmark/common/AsciiStringCaseConversionBenchmark.java => util/AsciiStringUtilCaseConversionBenchmark.java} (77%) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 322122b5b57..50524dcd48b 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -105,14 +105,26 @@ static boolean isUpperCase(byte value) { } private static long toLowerCase(long word) { - long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, - SWARByteUtil.UPPER_CASE_RANGE_PATTERN); + final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, + SWARByteUtil.UPPER_CASE_RANGE_PATTERN); + return word | mask >>> 2; + } + + private static int toLowerCase(int word) { + final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.UPPER_CASE_PATTERN, + (int) SWARByteUtil.UPPER_CASE_RANGE_PATTERN); return word | mask >>> 2; } private static long toUpperCase(long word) { - long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, - SWARByteUtil.LOWER_CASE_RANGE_PATTERN); + final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, + SWARByteUtil.LOWER_CASE_RANGE_PATTERN); + return word & ~(mask >>> 2); + } + + private static int toUpperCase(int word) { + final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.LOWER_CASE_PATTERN, + (int) SWARByteUtil.LOWER_CASE_RANGE_PATTERN); return word & ~(mask >>> 2); } @@ -145,12 +157,31 @@ static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { } fromIndex += Long.BYTES; } + final int byteCount = length & 7; + return unrolledConstainsUpperCase(bytes, fromIndex, byteCount); + } - for (; fromIndex < toIndex; ++fromIndex) { - byte value = bytes[fromIndex]; - if (isUpperCase(value)) { + private static boolean unrolledConstainsUpperCase(byte[] bytes, int fromIndex, int length) { + if ((length & 4) != 0) { + final int word = PlatformDependent.getInt(bytes, fromIndex); + final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.UPPER_CASE_PATTERN, + (int) SWARByteUtil.UPPER_CASE_RANGE_PATTERN); + if (mask != 0) { return true; } + fromIndex += Integer.BYTES; + } + if ((length & 2) != 0) { + if (isUpperCase(bytes[fromIndex])) { + return true; + } + if (isUpperCase(bytes[fromIndex + 1])) { + return true; + } + fromIndex += 2; + } + if ((length & 1) != 0) { + return isUpperCase(bytes[fromIndex]); } return false; } @@ -176,11 +207,30 @@ static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { } fromIndex += Long.BYTES; } + return unrolledContainsLowerCase(bytes, fromIndex, length & 7); + } - for (; fromIndex < toIndex; ++fromIndex) { + private static boolean unrolledContainsLowerCase(byte[] bytes, int fromIndex, int length) { + if ((length & 4) != 0) { + final int word = PlatformDependent.getInt(bytes, fromIndex); + final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.LOWER_CASE_PATTERN, + (int) SWARByteUtil.LOWER_CASE_RANGE_PATTERN); + if (mask != 0) { + return true; + } + fromIndex += Integer.BYTES; + } + if ((length & 2) != 0) { if (isLowerCase(bytes[fromIndex])) { return true; } + if (isLowerCase(bytes[fromIndex + 1])) { + return true; + } + fromIndex += 2; + } + if ((length & 1) != 0) { + return isLowerCase(bytes[fromIndex]); } return false; } @@ -200,10 +250,27 @@ static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int le srcPos += Long.BYTES; destPos += Long.BYTES; } + unrollToLowerCase(src, srcPos, dest, destPos, length & 7); + } - final int byteCount = length & 7; - for (int i = 0; i < byteCount; ++i) { - dest[destPos++] = toLowerCase(src[srcPos++]); + private static void unrollToLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + if ((length & 4) != 0) { + final int word = PlatformDependent.getInt(src, srcPos); + PlatformDependent.putInt(dest, destPos, toLowerCase(word)); + srcPos += Integer.BYTES; + destPos += Integer.BYTES; + } + if ((length & 2) != 0) { + PlatformDependent.putByte(dest, destPos, + toLowerCase(PlatformDependent.getByte(src, srcPos))); + PlatformDependent.putByte(dest, destPos + 1, + toLowerCase(PlatformDependent.getByte(src, srcPos + 1))); + srcPos += 2; + destPos += 2; + } + if ((length & 1) != 0) { + PlatformDependent.putByte(dest, destPos, + toLowerCase(PlatformDependent.getByte(src, srcPos))); } } @@ -224,8 +291,27 @@ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int le } final int byteCount = length & 7; - for (int i = 0; i < byteCount; ++i) { - dest[destPos++] = toUpperCase(src[srcPos++]); + unrolltoUpperCase(src, srcPos, dest, destPos, byteCount); + } + + private static void unrolltoUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + if ((length & 4) != 0) { + final int word = PlatformDependent.getInt(src, srcPos); + PlatformDependent.putInt(dest, destPos, toUpperCase(word)); + srcPos += Integer.BYTES; + destPos += Integer.BYTES; + } + if ((length & 2) != 0) { + PlatformDependent.putByte(dest, destPos, + toUpperCase(PlatformDependent.getByte(src, srcPos))); + PlatformDependent.putByte(dest, destPos + 1, + toUpperCase(PlatformDependent.getByte(src, srcPos + 1))); + srcPos += 2; + destPos += 2; + } + if ((length & 1) != 0) { + PlatformDependent.putByte(dest, destPos, + toUpperCase(PlatformDependent.getByte(src, srcPos))); } } @@ -247,12 +333,34 @@ static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, } } int byteCount = length & 7; - if (byteCount > 0) { - for (int i = 0; i < byteCount; ++i) { - if (toLowerCase(lhs[lhsPos++]) != toLowerCase(rhs[rhsPos++])) { - return false; - } + return unrollEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, byteCount); + } + + private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + if ((length & 4) != 0) { + final int lWord = PlatformDependent.getInt(lhs, lhsPos); + final int rWord = PlatformDependent.getInt(rhs, rhsPos); + if (toLowerCase(lWord) != toLowerCase(rWord)) { + return false; } + lhsPos += Integer.BYTES; + rhsPos += Integer.BYTES; + } + if ((length & 2) != 0) { + if (toLowerCase(PlatformDependent.getByte(lhs, lhsPos)) != + toLowerCase(PlatformDependent.getByte(rhs, rhsPos))) { + return false; + } + if (toLowerCase(PlatformDependent.getByte(lhs, lhsPos + 1)) != + toLowerCase(PlatformDependent.getByte(rhs, rhsPos + 1))) { + return false; + } + lhsPos += 2; + rhsPos += 2; + } + if ((length & 1) != 0) { + return toLowerCase(PlatformDependent.getByte(lhs, lhsPos)) == + toLowerCase(PlatformDependent.getByte(rhs, rhsPos)); } return true; } @@ -280,6 +388,13 @@ private static long applyPatternRange(long word, long lowPattern, long rangePatt return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); } + private static int applyPatternRange(int word, int lowPattern, int rangePattern) { + int input = (word | 0x80808080) - lowPattern; + input = ~((word | 0x7F7F7F7F) ^ input); + int tmp = (input & 0x7F7F7F7F) + rangePattern; + return ~(tmp | input | 0x7F7F7F7F); + } + private static long applyPattern(long word, long pattern) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; diff --git a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java similarity index 77% rename from microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java rename to microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java index 278aec6076c..a34fb048f03 100644 --- a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringCaseConversionBenchmark.java +++ b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java @@ -12,10 +12,9 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package io.netty.microbenchmark.common; +package io.netty.util; import io.netty.microbench.util.AbstractMicrobenchmark; -import io.netty.util.AsciiString; import io.netty.util.internal.SuppressJava6Requirement; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; @@ -38,9 +37,9 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 8, time = 1) @State(Scope.Benchmark) -public class AsciiStringCaseConversionBenchmark extends AbstractMicrobenchmark { +public class AsciiStringUtilCaseConversionBenchmark extends AbstractMicrobenchmark { - @Param({ "7", "16", "23", "32", "63" }) + @Param({ "7", "16", "23", "32" }) int size; @Param({ "11" }) int logPermutations; @@ -48,8 +47,8 @@ public class AsciiStringCaseConversionBenchmark extends AbstractMicrobenchmark { @Param({ "1" }) int seed; - int permutations; // uniquenessness - AsciiString[] upperCaseData; + int permutations; + byte[][] upperCaseData; private int i; @Param({ "true", "false" }) @@ -61,9 +60,9 @@ public void init() { System.setProperty("io.netty.noUnsafe", Boolean.valueOf(noUnsafe).toString()); SplittableRandom random = new SplittableRandom(seed); permutations = 1 << logPermutations; - upperCaseData = new AsciiString[permutations]; + upperCaseData = new byte[permutations][size]; for (int i = 0; i < permutations; ++i) { - final int foundIndex = random.nextInt(Math.max(0, Math.min(size - 8, size >> 1)), size); + final int foundIndex = random.nextInt(Math.max(0, size - 8), size); byte[] byteArray = new byte[size]; int j = 0; for (; j < size; j++) { @@ -71,24 +70,31 @@ public void init() { // turn any found value into something different if (j < foundIndex) { if (value >= 'A' && value <= 'Z') { - value = Character.toLowerCase(value); + value |= 32; } } if (j == foundIndex) { - value = 'G'; + value = 'N'; } byteArray[j] = (byte) value; } - upperCaseData[i] = new AsciiString(byteArray); + upperCaseData[i] = byteArray; } } - private AsciiString getData() { + private byte[] getData() { return upperCaseData[i++ & permutations - 1]; } @Benchmark - public AsciiString toLowerCase() { - return getData().toLowerCase(); + public byte[] toLowerCase() { + byte[] ret = new byte[size]; + AsciiStringUtil.toLowerCase(getData(), 0, ret, 0, size); + return ret; } -} \ No newline at end of file + + @Benchmark + public boolean containsUpperCase() { + return AsciiStringUtil.containsUpperCase(getData(), 0, size); + } +} From 25d1bf774d3fefeb8788406ca1a9d45decbb4a6c Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 15 Aug 2023 14:45:53 +0900 Subject: [PATCH 11/24] Apply case conversion algorithm from `folly` --- .../java/io/netty/util/AsciiStringUtil.java | 262 +++++++++++------- .../io/netty/util/AsciiStringUtilTest.java | 131 +++++++++ 2 files changed, 300 insertions(+), 93 deletions(-) create mode 100644 common/src/test/java/io/netty/util/AsciiStringUtilTest.java diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 50524dcd48b..374d1777c32 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -21,6 +21,10 @@ */ public final class AsciiStringUtil { + /** + * Returns index of the first occurrence of the specified byte in the specified array. + * this method utilizes SWAR technique to accelerate indexOf operation. + */ static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { if (!PlatformDependent.isUnaligned()) { return linearFirstIndexOf(bytes, fromIndex, toIndex, value); @@ -96,63 +100,50 @@ private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int byteCou return -1; } + /** + * Returns true if given byte is ascii lower case alphabet character. false otherwise. + */ static boolean isLowerCase(byte value) { return value >= 'a' && value <= 'z'; } + /** + * Returns true if given byte is ascii upper case alphabet character. false otherwise. + */ static boolean isUpperCase(byte value) { return value >= 'A' && value <= 'Z'; } - private static long toLowerCase(long word) { - final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, - SWARByteUtil.UPPER_CASE_RANGE_PATTERN); - return word | mask >>> 2; - } - - private static int toLowerCase(int word) { - final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.UPPER_CASE_PATTERN, - (int) SWARByteUtil.UPPER_CASE_RANGE_PATTERN); - return word | mask >>> 2; - } - - private static long toUpperCase(long word) { - final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, - SWARByteUtil.LOWER_CASE_RANGE_PATTERN); - return word & ~(mask >>> 2); - } - - private static int toUpperCase(int word) { - final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.LOWER_CASE_PATTERN, - (int) SWARByteUtil.LOWER_CASE_RANGE_PATTERN); - return word & ~(mask >>> 2); - } - + /** + * If the character is lowercase - converts the character to uppercase, + * otherwise returns the character as it is. Only for ASCII characters. + */ static byte toUpperCase(byte value) { return isLowerCase(value)? (byte) (value & ~32) : value; } + /** + * If the character is uppercase - converts the character to lowercase, + * otherwise returns the character as it is. Only for ASCII characters. + */ static byte toLowerCase(byte value) { return isUpperCase(value)? (byte) (value | 32) : value; } + /** + * Returns true if the given byte array contains at least one upper case character. false otherwise. + * This method utilizes SWAR technique to accelerate containsUpperCase operation. + */ static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { if (!PlatformDependent.isUnaligned()) { - for (int idx = fromIndex; idx < toIndex; ++idx) { - if (isUpperCase(bytes[idx])) { - return true; - } - } - return false; + return linearContainsUpperCase(bytes, fromIndex, toIndex); } final int length = toIndex - fromIndex; final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(bytes, fromIndex); - final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.UPPER_CASE_PATTERN, - SWARByteUtil.UPPER_CASE_RANGE_PATTERN); - if (mask != 0) { + if (SWARByteUtil.containsUpperCase(word)) { return true; } fromIndex += Long.BYTES; @@ -161,48 +152,52 @@ static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { return unrolledConstainsUpperCase(bytes, fromIndex, byteCount); } + private static boolean linearContainsUpperCase(byte[] bytes, int fromIndex, int toIndex) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (isUpperCase(bytes[idx])) { + return true; + } + } + return false; + } + private static boolean unrolledConstainsUpperCase(byte[] bytes, int fromIndex, int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); - final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.UPPER_CASE_PATTERN, - (int) SWARByteUtil.UPPER_CASE_RANGE_PATTERN); - if (mask != 0) { + if (SWARByteUtil.containsUpperCase(word)) { return true; } fromIndex += Integer.BYTES; } if ((length & 2) != 0) { - if (isUpperCase(bytes[fromIndex])) { + if (isUpperCase(PlatformDependent.getByte(bytes, fromIndex))) { return true; } - if (isUpperCase(bytes[fromIndex + 1])) { + if (isUpperCase(PlatformDependent.getByte(bytes, fromIndex + 1))) { return true; } fromIndex += 2; } if ((length & 1) != 0) { - return isUpperCase(bytes[fromIndex]); + return isUpperCase(PlatformDependent.getByte(bytes, fromIndex)); } return false; } + /** + * Returns true if the given byte array contains at least one lower case character. false otherwise. + * This method utilizes SWAR technique to accelerate containsLowerCase operation. + */ static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { if (!PlatformDependent.isUnaligned()) { - for (int idx = fromIndex; idx < toIndex; ++idx) { - if (isLowerCase(bytes[idx])) { - return true; - } - } - return false; + return linearContainsLowerCase(bytes, fromIndex, toIndex); } final int length = toIndex - fromIndex; final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(bytes, fromIndex); - final long mask = SWARByteUtil.applyPatternRange(word, SWARByteUtil.LOWER_CASE_PATTERN, - SWARByteUtil.LOWER_CASE_RANGE_PATTERN); - if (mask != 0) { + if (SWARByteUtil.containsLowerCase(word)) { return true; } fromIndex += Long.BYTES; @@ -210,53 +205,69 @@ static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { return unrolledContainsLowerCase(bytes, fromIndex, length & 7); } + private static boolean linearContainsLowerCase(byte[] bytes, int fromIndex, int toIndex) { + for (int idx = fromIndex; idx < toIndex; ++idx) { + if (isLowerCase(bytes[idx])) { + return true; + } + } + return false; + } + private static boolean unrolledContainsLowerCase(byte[] bytes, int fromIndex, int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); - final int mask = SWARByteUtil.applyPatternRange(word, (int) SWARByteUtil.LOWER_CASE_PATTERN, - (int) SWARByteUtil.LOWER_CASE_RANGE_PATTERN); - if (mask != 0) { + if (SWARByteUtil.containsLowerCase(word)) { return true; } fromIndex += Integer.BYTES; } if ((length & 2) != 0) { - if (isLowerCase(bytes[fromIndex])) { + if (isLowerCase(PlatformDependent.getByte(bytes, fromIndex))) { return true; } - if (isLowerCase(bytes[fromIndex + 1])) { + if (isLowerCase(PlatformDependent.getByte(bytes, fromIndex + 1))) { return true; } fromIndex += 2; } if ((length & 1) != 0) { - return isLowerCase(bytes[fromIndex]); + return isLowerCase(PlatformDependent.getByte(bytes, fromIndex)); } return false; } + /* + * Copies source byte array to a destination byte array, converting the characters to their + * corresponding lowercase. Only for ASCII characters. + */ + static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { if (!PlatformDependent.isUnaligned()) { - for (int i = 0; i < length; ++i) { - dest[destPos++] = toLowerCase(src[srcPos++]); - } + linearToLowerCase(src, srcPos, dest, destPos, length); return; } final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(src, srcPos); - PlatformDependent.putLong(dest, destPos, toLowerCase(word)); + PlatformDependent.putLong(dest, destPos, SWARByteUtil.toLowerCase(word)); srcPos += Long.BYTES; destPos += Long.BYTES; } unrollToLowerCase(src, srcPos, dest, destPos, length & 7); } + private static void linearToLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + for (int i = 0; i < length; ++i) { + dest[destPos++] = toLowerCase(src[srcPos++]); + } + } + private static void unrollToLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); - PlatformDependent.putInt(dest, destPos, toLowerCase(word)); + PlatformDependent.putInt(dest, destPos, SWARByteUtil.toLowerCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } @@ -274,18 +285,20 @@ private static void unrollToLowerCase(byte[] src, int srcPos, byte[] dest, int d } } + /* + * Copies source byte array to a destination byte array, converting the characters to their + * corresponding uppercase. Only for ASCII characters. + */ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { if (!PlatformDependent.isUnaligned()) { - for (int i = 0; i < length; ++i) { - dest[destPos++] = toUpperCase(src[srcPos++]); - } + linearToUpperCase(src, srcPos, dest, destPos, length); return; } final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(src, srcPos); - PlatformDependent.putLong(dest, destPos, toUpperCase(word)); + PlatformDependent.putLong(dest, destPos, SWARByteUtil.toUpperCase(word)); srcPos += Long.BYTES; destPos += Long.BYTES; } @@ -294,10 +307,16 @@ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int le unrolltoUpperCase(src, srcPos, dest, destPos, byteCount); } + private static void linearToUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + for (int i = 0; i < length; ++i) { + dest[destPos++] = toUpperCase(src[srcPos++]); + } + } + private static void unrolltoUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); - PlatformDependent.putInt(dest, destPos, toUpperCase(word)); + PlatformDependent.putInt(dest, destPos, SWARByteUtil.toUpperCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } @@ -315,24 +334,29 @@ private static void unrolltoUpperCase(byte[] src, int srcPos, byte[] dest, int d } } + /** + * Compares a portion of two source byte arrays for equality while ignoring case. This method compares the + * characters in a case-insensitive manner and returns true if the specified portions of the arrays are equal, + * false otherwise. + */ static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { if (lhs == rhs && lhsPos == rhsPos && lhs.length >= lhsPos + length) { return true; } - int longCount = length >>> 3; + final int longCount = length >>> 3; if (longCount > 0) { for (int i = 0; i < longCount; ++i) { final long lWord = PlatformDependent.getLong(lhs, lhsPos); final long rWord = PlatformDependent.getLong(rhs, rhsPos); - if (toLowerCase(lWord) != toLowerCase(rWord)) { + if (SWARByteUtil.toLowerCase(lWord) != SWARByteUtil.toLowerCase(rWord)) { return false; } lhsPos += Long.BYTES; rhsPos += Long.BYTES; } } - int byteCount = length & 7; + final int byteCount = length & 7; return unrollEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, byteCount); } @@ -340,7 +364,7 @@ private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs if ((length & 4) != 0) { final int lWord = PlatformDependent.getInt(lhs, lhsPos); final int rWord = PlatformDependent.getInt(rhs, rhsPos); - if (toLowerCase(lWord) != toLowerCase(rWord)) { + if (SWARByteUtil.toLowerCase(lWord) != SWARByteUtil.toLowerCase(rWord)) { return false; } lhsPos += Integer.BYTES; @@ -366,35 +390,10 @@ private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs } public static final class SWARByteUtil { - static final long UPPER_CASE_PATTERN = compilePattern((byte) 'A'); - static final long UPPER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'A', (byte) 'Z'); - - static final long LOWER_CASE_PATTERN = compilePattern((byte) 'a'); - static final long LOWER_CASE_RANGE_PATTERN = compileRangePattern((byte) 'a', (byte) 'z'); - public static long compilePattern(byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } - private static long compileRangePattern(byte low, byte high) { - assert low <= high && high - low <= 128; - return (0x7F7F - high + low & 0xFFL) * 0x101010101010101L; - } - - private static long applyPatternRange(long word, long lowPattern, long rangePattern) { - long input = (word | 0x8080808080808080L) - lowPattern; - input = ~((word | 0x7F7F7F7F7F7F7F7FL) ^ input); - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + rangePattern; - return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - } - - private static int applyPatternRange(int word, int lowPattern, int rangePattern) { - int input = (word | 0x80808080) - lowPattern; - input = ~((word | 0x7F7F7F7F) ^ input); - int tmp = (input & 0x7F7F7F7F) + rangePattern; - return ~(tmp | input | 0x7F7F7F7F); - } - private static long applyPattern(long word, long pattern) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; @@ -413,6 +412,83 @@ public static int firstAnyPattern(long word, long pattern, boolean leading) { return binaryPosition >>> 3; } + private static long applyUpperCasePattern(long word) { + long rotated = word & 0x7F7F7F7F7F7F7F7FL; + rotated += 0x2525252525252525L; + rotated &= 0x7F7F7F7F7F7F7F7FL; + rotated += 0x1A1A1A1A1A1A1A1AL; + rotated &= ~word; + rotated &= 0x8080808080808080L; + return rotated; + } + + + private static int applyUpperCasePattern(int word) { + int rotated = word & 0x7F7F7F7F; + rotated += 0x25252525; + rotated &= 0x7F7F7F7F; + rotated += 0x1A1A1A1A; + rotated &= ~word; + rotated &= 0x80808080; + return rotated; + } + + private static long applyLowerCasePattern(long word) { + long rotated = word & 0x7F7F7F7F7F7F7F7FL; + rotated += 0x0505050505050505L; + rotated &= 0x7F7F7F7F7F7F7F7FL; + rotated += 0x1A1A1A1A1A1A1A1AL; + rotated &= ~word; + rotated &= 0x8080808080808080L; + return rotated; + } + + private static int applyLowerCasePattern(int word) { + int rotated = word & 0x7F7F7F7F; + rotated += 0x05050505; + rotated &= 0x7F7F7F7F; + rotated += 0x1A1A1A1A; + rotated &= ~word; + rotated &= 0x80808080; + return rotated; + } + + static boolean containsUpperCase(long word) { + return applyUpperCasePattern(word) != 0; + } + + static boolean containsUpperCase(int word) { + return applyUpperCasePattern(word) != 0; + } + + static boolean containsLowerCase(long word) { + return applyLowerCasePattern(word) != 0; + } + + static boolean containsLowerCase(int word) { + return applyLowerCasePattern(word) != 0; + } + + static long toLowerCase(long word) { + final long mask = applyUpperCasePattern(word) >>> 2; + return word | mask; + } + + static int toLowerCase(int word) { + final int mask = applyUpperCasePattern(word) >>> 2; + return word | mask; + } + + static long toUpperCase(long word) { + final long mask = applyLowerCasePattern(word) >>> 2; + return word & ~mask; + } + + static int toUpperCase(int word) { + final int mask = applyLowerCasePattern(word) >>> 2; + return word & ~mask; + } + private SWARByteUtil() { } } diff --git a/common/src/test/java/io/netty/util/AsciiStringUtilTest.java b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java new file mode 100644 index 00000000000..b0bfd7697a5 --- /dev/null +++ b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.netty.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsciiStringUtilTest { + + @Test + public void toLowerCaseTest() { + final byte[] eAsciiTable = getExtendedAsciiTable(); + // test single byte + for (int i = 0; i < eAsciiTable.length; i++) { + final byte b = eAsciiTable[i]; + final byte expected = 'A' <= b && b <= 'Z'? (byte) Character.toLowerCase(b) : b; + final byte actual = AsciiStringUtil.toLowerCase(b); + assertEquals(expected, actual); + } + + + // test 4 bytes + for (int i = 0; i < eAsciiTable.length; i += 4) { + final int word = getInt(eAsciiTable, i); + final int converted = AsciiStringUtil.SWARByteUtil.toLowerCase(word); + for (int j = 0; j < Integer.BYTES; ++j) { + final byte expected = AsciiStringUtil.toLowerCase(eAsciiTable[i + j]); + final byte actual = getByte(converted, j); + assertEquals(expected, actual); + } + } + + // test 8 bytes + for (int i = 0; i < eAsciiTable.length; i += 8) { + final long word = getLong(eAsciiTable, i); + final long converted = AsciiStringUtil.SWARByteUtil.toLowerCase(word); + for (int j = 0; j < Long.BYTES; ++j) { + final byte expected = AsciiStringUtil.toLowerCase(eAsciiTable[i + j]); + final byte actual = getByte(converted, j); + assertEquals(expected, actual); + } + } + } + + @Test + public void toUpperCaseTest() { + final byte[] eAsciiTable = getExtendedAsciiTable(); + // test single byte + for (int i = 0; i < eAsciiTable.length; i++) { + final byte b = eAsciiTable[i]; + final byte expected = 'a' <= b && b <= 'z'? (byte) Character.toUpperCase(b) : b; + final byte actual = AsciiStringUtil.toUpperCase(b); + assertEquals(expected, actual); + } + + // test 4 bytes + for (int i = 0; i < eAsciiTable.length; i += 4) { + final int word = getInt(eAsciiTable, i); + final int converted = AsciiStringUtil.SWARByteUtil.toUpperCase(word); + for (int j = 0; j < Integer.BYTES; ++j) { + final byte expected = AsciiStringUtil.toUpperCase(eAsciiTable[i + j]); + final byte actual = getByte(converted, j); + assertEquals(expected, actual); + } + } + + // test 8 bytes + for (int i = 0; i < eAsciiTable.length; i += 8) { + final long word = getLong(eAsciiTable, i); + final long converted = AsciiStringUtil.SWARByteUtil.toUpperCase(word); + for (int j = 0; j < Long.BYTES; ++j) { + final byte expected = AsciiStringUtil.toUpperCase(eAsciiTable[i + j]); + final byte actual = getByte(converted, j); + assertEquals(expected, actual); + } + } + } + + private static long getLong(byte[] bytes, int idx) { + assert idx >= 0 && bytes.length >= idx + 8; + return (long) bytes[idx] << 56 | + ((long) bytes[idx + 1] & 0xff) << 48 | + ((long) bytes[idx + 2] & 0xff) << 40 | + ((long) bytes[idx + 3] & 0xff) << 32 | + ((long) bytes[idx + 4] & 0xff) << 24 | + ((long) bytes[idx + 5] & 0xff) << 16 | + ((long) bytes[idx + 6] & 0xff) << 8 | + (long) bytes[idx + 7] & 0xff; + } + + private static int getInt(byte[] bytes, int idx) { + assert idx >= 0 && bytes.length >= idx + 4; + return bytes[idx] << 24 | + (bytes[idx + 1] & 0xff) << 16 | + (bytes[idx + 2] & 0xff) << 8 | + bytes[idx + 3] & 0xff; + } + + private static byte getByte(long word, int idx) { + assert idx >= 0 && idx < 8; + return (byte) (word >>> (7 - idx) * 8 & 0xff); + } + + + private static byte getByte(int word, int idx) { + assert idx >= 0 && idx < 4; + return (byte) (word >>> (3 - idx) * 8 & 0xff); + } + + private static byte[] getExtendedAsciiTable() { + final byte[] table = new byte[256]; + for (int i = 0; i < 256; i++) { + table[i] = (byte) i; + } + return table; + } + +} \ No newline at end of file From 79cd5fd127c18c9460842013beaf380627833c2e Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 15 Aug 2023 14:54:02 +0900 Subject: [PATCH 12/24] Resolve Checkstyle complaint --- common/src/main/java/io/netty/util/AsciiStringUtil.java | 1 - common/src/test/java/io/netty/util/AsciiStringUtilTest.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 374d1777c32..1590c545ccb 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -422,7 +422,6 @@ private static long applyUpperCasePattern(long word) { return rotated; } - private static int applyUpperCasePattern(int word) { int rotated = word & 0x7F7F7F7F; rotated += 0x25252525; diff --git a/common/src/test/java/io/netty/util/AsciiStringUtilTest.java b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java index b0bfd7697a5..f0cb55ee19c 100644 --- a/common/src/test/java/io/netty/util/AsciiStringUtilTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java @@ -31,7 +31,6 @@ public void toLowerCaseTest() { assertEquals(expected, actual); } - // test 4 bytes for (int i = 0; i < eAsciiTable.length; i += 4) { final int word = getInt(eAsciiTable, i); @@ -114,7 +113,6 @@ private static byte getByte(long word, int idx) { return (byte) (word >>> (7 - idx) * 8 & 0xff); } - private static byte getByte(int word, int idx) { assert idx >= 0 && idx < 4; return (byte) (word >>> (3 - idx) * 8 & 0xff); @@ -128,4 +126,4 @@ private static byte[] getExtendedAsciiTable() { return table; } -} \ No newline at end of file +} From 7f97110e2d8fb4a60065f938c681f3a86ce19949 Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 15 Aug 2023 15:33:00 +0900 Subject: [PATCH 13/24] Improve AsciiStringUtilCaseConversionBenchmark --- .../common => util}/AsciiStringIndexOfBenchmark.java | 5 ++--- .../netty/util/AsciiStringUtilCaseConversionBenchmark.java | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) rename microbench/src/main/java/io/netty/{microbenchmark/common => util}/AsciiStringIndexOfBenchmark.java (97%) diff --git a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java b/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java similarity index 97% rename from microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java rename to microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java index 84995b32616..ba07099fec1 100644 --- a/microbench/src/main/java/io/netty/microbenchmark/common/AsciiStringIndexOfBenchmark.java +++ b/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java @@ -12,10 +12,9 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package io.netty.microbenchmark.common; +package io.netty.util; import io.netty.microbench.util.AbstractMicrobenchmark; -import io.netty.util.AsciiString; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.SuppressJava6Requirement; import org.openjdk.jmh.annotations.Benchmark; @@ -44,7 +43,7 @@ public class AsciiStringIndexOfBenchmark extends AbstractMicrobenchmark { public static Object blackhole; @Param({ "7", "16", "23", "32" }) int size; - @Param({ "11" }) + @Param({ "4", "11" }) int logPermutations; @Param({ "1" }) diff --git a/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java index a34fb048f03..829ada56f3f 100644 --- a/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java +++ b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java @@ -41,7 +41,7 @@ public class AsciiStringUtilCaseConversionBenchmark extends AbstractMicrobenchma @Param({ "7", "16", "23", "32" }) int size; - @Param({ "11" }) + @Param({ "4", "11" }) int logPermutations; @Param({ "1" }) @@ -49,6 +49,8 @@ public class AsciiStringUtilCaseConversionBenchmark extends AbstractMicrobenchma int permutations; byte[][] upperCaseData; + + byte[] ret; private int i; @Param({ "true", "false" }) @@ -60,6 +62,7 @@ public void init() { System.setProperty("io.netty.noUnsafe", Boolean.valueOf(noUnsafe).toString()); SplittableRandom random = new SplittableRandom(seed); permutations = 1 << logPermutations; + ret = new byte[size]; upperCaseData = new byte[permutations][size]; for (int i = 0; i < permutations; ++i) { final int foundIndex = random.nextInt(Math.max(0, size - 8), size); @@ -88,7 +91,6 @@ private byte[] getData() { @Benchmark public byte[] toLowerCase() { - byte[] ret = new byte[size]; AsciiStringUtil.toLowerCase(getData(), 0, ret, 0, size); return ret; } From 64f72ae8e059cc311c67e068d3a717954e59f09e Mon Sep 17 00:00:00 2001 From: jchrys Date: Wed, 16 Aug 2023 00:04:20 +0900 Subject: [PATCH 14/24] notUnaligned case for equalsIgnoreCases --- .../main/java/io/netty/util/AsciiStringUtil.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 1590c545ccb..a4beff633c5 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -344,6 +344,10 @@ static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, return true; } + if (!PlatformDependent.isUnaligned()) { + return linearEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, length); + } + final int longCount = length >>> 3; if (longCount > 0) { for (int i = 0; i < longCount; ++i) { @@ -360,6 +364,15 @@ static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, return unrollEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, byteCount); } + private static boolean linearEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + for (int i = 0; i < length; ++i) { + if (toLowerCase(lhs[lhsPos + i]) != toLowerCase(rhs[rhsPos + i])) { + return false; + } + } + return true; + } + private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { if ((length & 4) != 0) { final int lWord = PlatformDependent.getInt(lhs, lhsPos); From 01be7cb7328ce5cfbfe4a075dc2c1654010c1369 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sat, 19 Aug 2023 22:21:55 +0900 Subject: [PATCH 15/24] Apply final --- .../main/java/io/netty/util/AsciiString.java | 9 +- .../java/io/netty/util/AsciiStringUtil.java | 84 ++++++++++--------- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 7053068848b..5dfb7fff47c 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -723,6 +723,7 @@ public int indexOf(char ch, int start) { } final byte chAsByte = c2b0(ch); + final int offset = this.offset; final int fromIndex = start + offset; final int toIndex = offset + length; final int index = AsciiStringUtil.firstIndexOf(value, fromIndex, toIndex, chAsByte); @@ -924,8 +925,8 @@ public AsciiString toLowerCase() { return this; } - final byte[] newValue = PlatformDependent.allocateUninitializedArray(length()); - AsciiStringUtil.toLowerCase(value, offset, newValue, 0, length()); + final byte[] newValue = PlatformDependent.allocateUninitializedArray(length); + AsciiStringUtil.toLowerCase(value, offset, newValue, 0, length); return new AsciiString(newValue, false); } @@ -938,8 +939,8 @@ public AsciiString toUpperCase() { if (!AsciiStringUtil.containsLowerCase(value, offset, offset + length)) { return this; } - final byte[] newValue = PlatformDependent.allocateUninitializedArray(length()); - AsciiStringUtil.toUpperCase(value, offset, newValue, 0, length()); + final byte[] newValue = PlatformDependent.allocateUninitializedArray(length); + AsciiStringUtil.toUpperCase(value, offset, newValue, 0, length); return new AsciiString(newValue, false); } diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index a4beff633c5..916429c6545 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -25,7 +25,7 @@ public final class AsciiStringUtil { * Returns index of the first occurrence of the specified byte in the specified array. * this method utilizes SWAR technique to accelerate indexOf operation. */ - static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + static int firstIndexOf(final byte[] bytes, int fromIndex, final int toIndex, final byte value) { if (!PlatformDependent.isUnaligned()) { return linearFirstIndexOf(bytes, fromIndex, toIndex, value); } @@ -44,7 +44,8 @@ static int firstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { return unrolledFirstIndexOf(bytes, fromIndex, byteCount, value); } - private static int linearFirstIndexOf(byte[] bytes, int fromIndex, int toIndex, byte value) { + private static int linearFirstIndexOf(final byte[] bytes, final int fromIndex, + final int toIndex, final byte value) { for (int idx = fromIndex; idx < toIndex; ++idx) { if (bytes[idx] == value) { return idx; @@ -53,7 +54,8 @@ private static int linearFirstIndexOf(byte[] bytes, int fromIndex, int toIndex, return -1; } - private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int byteCount, byte value) { + private static int unrolledFirstIndexOf(final byte[] bytes, final int fromIndex, + final int byteCount, final byte value) { assert byteCount >= 0 && byteCount < 8; if (byteCount == 0) { return -1; @@ -103,14 +105,14 @@ private static int unrolledFirstIndexOf(byte[] bytes, int fromIndex, int byteCou /** * Returns true if given byte is ascii lower case alphabet character. false otherwise. */ - static boolean isLowerCase(byte value) { + static boolean isLowerCase(final byte value) { return value >= 'a' && value <= 'z'; } /** * Returns true if given byte is ascii upper case alphabet character. false otherwise. */ - static boolean isUpperCase(byte value) { + static boolean isUpperCase(final byte value) { return value >= 'A' && value <= 'Z'; } @@ -118,7 +120,7 @@ static boolean isUpperCase(byte value) { * If the character is lowercase - converts the character to uppercase, * otherwise returns the character as it is. Only for ASCII characters. */ - static byte toUpperCase(byte value) { + static byte toUpperCase(final byte value) { return isLowerCase(value)? (byte) (value & ~32) : value; } @@ -126,7 +128,7 @@ static byte toUpperCase(byte value) { * If the character is uppercase - converts the character to lowercase, * otherwise returns the character as it is. Only for ASCII characters. */ - static byte toLowerCase(byte value) { + static byte toLowerCase(final byte value) { return isUpperCase(value)? (byte) (value | 32) : value; } @@ -134,7 +136,7 @@ static byte toLowerCase(byte value) { * Returns true if the given byte array contains at least one upper case character. false otherwise. * This method utilizes SWAR technique to accelerate containsUpperCase operation. */ - static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { + static boolean containsUpperCase(final byte[] bytes, int fromIndex, final int toIndex) { if (!PlatformDependent.isUnaligned()) { return linearContainsUpperCase(bytes, fromIndex, toIndex); } @@ -152,7 +154,7 @@ static boolean containsUpperCase(byte[] bytes, int fromIndex, int toIndex) { return unrolledConstainsUpperCase(bytes, fromIndex, byteCount); } - private static boolean linearContainsUpperCase(byte[] bytes, int fromIndex, int toIndex) { + private static boolean linearContainsUpperCase(final byte[] bytes, final int fromIndex, final int toIndex) { for (int idx = fromIndex; idx < toIndex; ++idx) { if (isUpperCase(bytes[idx])) { return true; @@ -161,7 +163,7 @@ private static boolean linearContainsUpperCase(byte[] bytes, int fromIndex, int return false; } - private static boolean unrolledConstainsUpperCase(byte[] bytes, int fromIndex, int length) { + private static boolean unrolledConstainsUpperCase(final byte[] bytes, int fromIndex, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); if (SWARByteUtil.containsUpperCase(word)) { @@ -188,7 +190,7 @@ private static boolean unrolledConstainsUpperCase(byte[] bytes, int fromIndex, i * Returns true if the given byte array contains at least one lower case character. false otherwise. * This method utilizes SWAR technique to accelerate containsLowerCase operation. */ - static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { + static boolean containsLowerCase(final byte[] bytes, int fromIndex, final int toIndex) { if (!PlatformDependent.isUnaligned()) { return linearContainsLowerCase(bytes, fromIndex, toIndex); } @@ -205,7 +207,7 @@ static boolean containsLowerCase(byte[] bytes, int fromIndex, int toIndex) { return unrolledContainsLowerCase(bytes, fromIndex, length & 7); } - private static boolean linearContainsLowerCase(byte[] bytes, int fromIndex, int toIndex) { + private static boolean linearContainsLowerCase(final byte[] bytes, final int fromIndex, final int toIndex) { for (int idx = fromIndex; idx < toIndex; ++idx) { if (isLowerCase(bytes[idx])) { return true; @@ -214,7 +216,7 @@ private static boolean linearContainsLowerCase(byte[] bytes, int fromIndex, int return false; } - private static boolean unrolledContainsLowerCase(byte[] bytes, int fromIndex, int length) { + private static boolean unrolledContainsLowerCase(final byte[] bytes, int fromIndex, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); if (SWARByteUtil.containsLowerCase(word)) { @@ -242,7 +244,7 @@ private static boolean unrolledContainsLowerCase(byte[] bytes, int fromIndex, in * corresponding lowercase. Only for ASCII characters. */ - static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + static void toLowerCase(final byte[] src, int srcPos, final byte[] dest, int destPos, final int length) { if (!PlatformDependent.isUnaligned()) { linearToLowerCase(src, srcPos, dest, destPos, length); return; @@ -258,13 +260,15 @@ static void toLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int le unrollToLowerCase(src, srcPos, dest, destPos, length & 7); } - private static void linearToLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + private static void linearToLowerCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int length) { for (int i = 0; i < length; ++i) { dest[destPos++] = toLowerCase(src[srcPos++]); } } - private static void unrollToLowerCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + private static void unrollToLowerCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); PlatformDependent.putInt(dest, destPos, SWARByteUtil.toLowerCase(word)); @@ -289,7 +293,7 @@ private static void unrollToLowerCase(byte[] src, int srcPos, byte[] dest, int d * Copies source byte array to a destination byte array, converting the characters to their * corresponding uppercase. Only for ASCII characters. */ - static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + static void toUpperCase(final byte[] src, int srcPos, final byte[] dest, int destPos, final int length) { if (!PlatformDependent.isUnaligned()) { linearToUpperCase(src, srcPos, dest, destPos, length); return; @@ -307,13 +311,15 @@ static void toUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int le unrolltoUpperCase(src, srcPos, dest, destPos, byteCount); } - private static void linearToUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + private static void linearToUpperCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int length) { for (int i = 0; i < length; ++i) { dest[destPos++] = toUpperCase(src[srcPos++]); } } - private static void unrolltoUpperCase(byte[] src, int srcPos, byte[] dest, int destPos, int length) { + private static void unrolltoUpperCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); PlatformDependent.putInt(dest, destPos, SWARByteUtil.toUpperCase(word)); @@ -339,7 +345,7 @@ private static void unrolltoUpperCase(byte[] src, int srcPos, byte[] dest, int d * characters in a case-insensitive manner and returns true if the specified portions of the arrays are equal, * false otherwise. */ - static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + static boolean equalsIgnoreCases(final byte[] lhs, int lhsPos, final byte[] rhs, int rhsPos, final int length) { if (lhs == rhs && lhsPos == rhsPos && lhs.length >= lhsPos + length) { return true; } @@ -364,7 +370,8 @@ static boolean equalsIgnoreCases(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, return unrollEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, byteCount); } - private static boolean linearEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + private static boolean linearEqualsIgnoreCase(final byte[] lhs, int lhsPos, + final byte[] rhs, int rhsPos, final int length) { for (int i = 0; i < length; ++i) { if (toLowerCase(lhs[lhsPos + i]) != toLowerCase(rhs[rhsPos + i])) { return false; @@ -373,7 +380,8 @@ private static boolean linearEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs return true; } - private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs, int rhsPos, int length) { + private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, + final byte[] rhs, int rhsPos, final int length) { if ((length & 4) != 0) { final int lWord = PlatformDependent.getInt(lhs, lhsPos); final int rWord = PlatformDependent.getInt(rhs, rhsPos); @@ -403,21 +411,21 @@ private static boolean unrollEqualsIgnoreCase(byte[] lhs, int lhsPos, byte[] rhs } public static final class SWARByteUtil { - public static long compilePattern(byte byteToFind) { + public static long compilePattern(final byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } - private static long applyPattern(long word, long pattern) { + private static long applyPattern(final long word, final long pattern) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); } - public static int getIndex(long mask, boolean isBigEndian) { + public static int getIndex(final long mask, final boolean isBigEndian) { return isBigEndian? Long.numberOfLeadingZeros(mask) >>> 3 : Long.numberOfTrailingZeros(mask) >>> 3; } - public static int firstAnyPattern(long word, long pattern, boolean leading) { + public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); @@ -425,7 +433,7 @@ public static int firstAnyPattern(long word, long pattern, boolean leading) { return binaryPosition >>> 3; } - private static long applyUpperCasePattern(long word) { + private static long applyUpperCasePattern(final long word) { long rotated = word & 0x7F7F7F7F7F7F7F7FL; rotated += 0x2525252525252525L; rotated &= 0x7F7F7F7F7F7F7F7FL; @@ -435,7 +443,7 @@ private static long applyUpperCasePattern(long word) { return rotated; } - private static int applyUpperCasePattern(int word) { + private static int applyUpperCasePattern(final int word) { int rotated = word & 0x7F7F7F7F; rotated += 0x25252525; rotated &= 0x7F7F7F7F; @@ -445,7 +453,7 @@ private static int applyUpperCasePattern(int word) { return rotated; } - private static long applyLowerCasePattern(long word) { + private static long applyLowerCasePattern(final long word) { long rotated = word & 0x7F7F7F7F7F7F7F7FL; rotated += 0x0505050505050505L; rotated &= 0x7F7F7F7F7F7F7F7FL; @@ -455,7 +463,7 @@ private static long applyLowerCasePattern(long word) { return rotated; } - private static int applyLowerCasePattern(int word) { + private static int applyLowerCasePattern(final int word) { int rotated = word & 0x7F7F7F7F; rotated += 0x05050505; rotated &= 0x7F7F7F7F; @@ -465,38 +473,38 @@ private static int applyLowerCasePattern(int word) { return rotated; } - static boolean containsUpperCase(long word) { + static boolean containsUpperCase(final long word) { return applyUpperCasePattern(word) != 0; } - static boolean containsUpperCase(int word) { + static boolean containsUpperCase(final int word) { return applyUpperCasePattern(word) != 0; } - static boolean containsLowerCase(long word) { + static boolean containsLowerCase(final long word) { return applyLowerCasePattern(word) != 0; } - static boolean containsLowerCase(int word) { + static boolean containsLowerCase(final int word) { return applyLowerCasePattern(word) != 0; } - static long toLowerCase(long word) { + static long toLowerCase(final long word) { final long mask = applyUpperCasePattern(word) >>> 2; return word | mask; } - static int toLowerCase(int word) { + static int toLowerCase(final int word) { final int mask = applyUpperCasePattern(word) >>> 2; return word | mask; } - static long toUpperCase(long word) { + static long toUpperCase(final long word) { final long mask = applyLowerCasePattern(word) >>> 2; return word & ~mask; } - static int toUpperCase(int word) { + static int toUpperCase(final int word) { final int mask = applyLowerCasePattern(word) >>> 2; return word & ~mask; } From 4e6147166c224e45ba88a16865383edf20581c75 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 20 Aug 2023 00:56:59 +0900 Subject: [PATCH 16/24] Enhance JavaDoc --- .../java/io/netty/util/AsciiStringUtil.java | 84 ++++++++++++++++--- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 916429c6545..4d4567523d8 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -410,29 +410,56 @@ private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, return true; } + /** + * A collection methods for performing operations using SWAR (SIMD within a register) techniques. + */ public static final class SWARByteUtil { + + /** + * Compiles given byte into a long pattern suitable for SWAR operations. + */ public static long compilePattern(final byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } - private static long applyPattern(final long word, final long pattern) { - long input = word ^ pattern; - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; - return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - } - - public static int getIndex(final long mask, final boolean isBigEndian) { - return isBigEndian? Long.numberOfLeadingZeros(mask) >>> 3 : Long.numberOfTrailingZeros(mask) >>> 3; + /** + * Returns the index of the first occurrence of the specified pattern in the specified word. + * If no pattern is found, returns 8. + */ + public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { + long tmp = applyPattern(word, pattern); + final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); + return binaryPosition >>> 3; } - public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { + /** + * Applies a compiled pattern to given word. + * Returns a word where each byte that matches the pattern has the highest bit set. + */ + private static long applyPattern(final long word, final long pattern) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; - tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; + return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); } + /** + * Returns the index of the first occurrence of byte that specificied in the pattern. + * If no pattern is found, returns 8. + * + * @param word the return value of {@link #applyPattern(long, long)} + * @param isNative if ture, if given word is big endian + * if false, if given word is little endian + * @return the index of the first occurrence of the specified pattern in the specified word. + * If no pattern is found, returns 8. + */ + public static int getIndex(final long word, final boolean isNative) { + final int tmp = isNative? Long.numberOfLeadingZeros(word) : Long.numberOfTrailingZeros(word); + return tmp >>> 3; + } + + /** + * Returns a word where each ASCII uppercase byte has the highest bit set. + */ private static long applyUpperCasePattern(final long word) { long rotated = word & 0x7F7F7F7F7F7F7F7FL; rotated += 0x2525252525252525L; @@ -443,6 +470,9 @@ private static long applyUpperCasePattern(final long word) { return rotated; } + /** + * Returns a word where each ASCII uppercase byte has the highest bit set. + */ private static int applyUpperCasePattern(final int word) { int rotated = word & 0x7F7F7F7F; rotated += 0x25252525; @@ -453,6 +483,9 @@ private static int applyUpperCasePattern(final int word) { return rotated; } + /** + * Returns a word where each ASCII lowercase byte has the highest bit set. + */ private static long applyLowerCasePattern(final long word) { long rotated = word & 0x7F7F7F7F7F7F7F7FL; rotated += 0x0505050505050505L; @@ -463,6 +496,9 @@ private static long applyLowerCasePattern(final long word) { return rotated; } + /** + * Returns a word where each lowercase ASCII byte has the highest bit set. + */ private static int applyLowerCasePattern(final int word) { int rotated = word & 0x7F7F7F7F; rotated += 0x05050505; @@ -473,37 +509,61 @@ private static int applyLowerCasePattern(final int word) { return rotated; } + /** + * Returns true if the given word contains at least one ASCII uppercase byte. + */ static boolean containsUpperCase(final long word) { return applyUpperCasePattern(word) != 0; } + /** + * Returns true if the given word contains at least one ASCII uppercase byte. + */ static boolean containsUpperCase(final int word) { return applyUpperCasePattern(word) != 0; } + /** + * Returns true if the given word contains at least one ASCII lowercase byte. + */ static boolean containsLowerCase(final long word) { return applyLowerCasePattern(word) != 0; } + /** + * Returns true if the given word contains at least one ASCII lowercase byte. + */ static boolean containsLowerCase(final int word) { return applyLowerCasePattern(word) != 0; } + /** + * Returns a word with all bytes converted to lowercase ASCII. + */ static long toLowerCase(final long word) { final long mask = applyUpperCasePattern(word) >>> 2; return word | mask; } + /** + * Returns a word with all bytes converted to lowercase ASCII. + */ static int toLowerCase(final int word) { final int mask = applyUpperCasePattern(word) >>> 2; return word | mask; } + /** + * Returns a word with all bytes converted to uppercase ASCII. + */ static long toUpperCase(final long word) { final long mask = applyLowerCasePattern(word) >>> 2; return word & ~mask; } + /** + * Returns a word with all bytes converted to uppercase ASCII. + */ static int toUpperCase(final int word) { final int mask = applyLowerCasePattern(word) >>> 2; return word & ~mask; From 74dcd8801e866f57912b5f651b35b0c56500a935 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 20 Aug 2023 02:16:06 +0900 Subject: [PATCH 17/24] Format AsciiStringIndexOfBenchmark --- .../main/java/io/netty/util/AsciiStringIndexOfBenchmark.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java b/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java index ba07099fec1..a5996847551 100644 --- a/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java +++ b/microbench/src/main/java/io/netty/util/AsciiStringIndexOfBenchmark.java @@ -43,6 +43,7 @@ public class AsciiStringIndexOfBenchmark extends AbstractMicrobenchmark { public static Object blackhole; @Param({ "7", "16", "23", "32" }) int size; + @Param({ "4", "11" }) int logPermutations; @@ -50,7 +51,9 @@ public class AsciiStringIndexOfBenchmark extends AbstractMicrobenchmark { int seed; int permutations; + AsciiString[] data; + private int i; @Param({ "0" }) From 7678ecddbf5316756e64319a6d34ba968cfc974f Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 20 Aug 2023 02:17:13 +0900 Subject: [PATCH 18/24] Format AsciiStringUtilCaseConversionBenchmark --- .../netty/util/AsciiStringUtilCaseConversionBenchmark.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java index 829ada56f3f..b9e6280170a 100644 --- a/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java +++ b/microbench/src/main/java/io/netty/util/AsciiStringUtilCaseConversionBenchmark.java @@ -41,16 +41,19 @@ public class AsciiStringUtilCaseConversionBenchmark extends AbstractMicrobenchma @Param({ "7", "16", "23", "32" }) int size; + @Param({ "4", "11" }) int logPermutations; - @Param({ "1" }) + @Param({ "0" }) int seed; int permutations; + byte[][] upperCaseData; byte[] ret; + private int i; @Param({ "true", "false" }) From baa5dfba49c1652aef3428b9eff361958c9cfc7e Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 17 Mar 2024 22:27:24 +0900 Subject: [PATCH 19/24] Separate SWARUtil --- .../java/io/netty/buffer/ByteBufUtil.java | 6 +- .../main/java/io/netty/util/AsciiString.java | 2 +- .../java/io/netty/util/AsciiStringUtil.java | 190 ++---------------- .../java/io/netty/util/internal/SWARUtil.java | 164 +++++++++++++++ .../io/netty/util/AsciiStringUtilTest.java | 9 +- 5 files changed, 187 insertions(+), 184 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/SWARUtil.java diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index 682201e5115..13845c26c4b 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -16,7 +16,6 @@ package io.netty.buffer; import io.netty.util.AsciiString; -import io.netty.util.AsciiStringUtil.SWARByteUtil; import io.netty.util.ByteProcessor; import io.netty.util.CharsetUtil; import io.netty.util.IllegalReferenceCountException; @@ -28,6 +27,7 @@ import io.netty.util.internal.ObjectPool.Handle; import io.netty.util.internal.ObjectPool.ObjectCreator; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SWARUtil; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.logging.InternalLogger; @@ -590,11 +590,11 @@ static int firstIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte final ByteOrder nativeOrder = ByteOrder.nativeOrder(); final boolean isNative = nativeOrder == buffer.order(); final boolean useLE = nativeOrder == ByteOrder.LITTLE_ENDIAN; - final long pattern = SWARByteUtil.compilePattern(value); + final long pattern = SWARUtil.compilePattern(value); for (int i = 0; i < longCount; i++) { // use the faster available getLong final long word = useLE? buffer._getLongLE(offset) : buffer._getLong(offset); - int index = SWARByteUtil.firstAnyPattern(word, pattern, isNative); + int index = SWARUtil.firstAnyPattern(word, pattern, isNative); if (index < Long.BYTES) { return offset + index; } diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 5dfb7fff47c..b742f03934f 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -535,7 +535,7 @@ public boolean contentEqualsIgnoreCase(CharSequence string) { if (string instanceof AsciiString) { AsciiString rhs = (AsciiString) string; - return AsciiStringUtil.equalsIgnoreCases(value, offset, rhs.value, rhs.offset, length()); + return AsciiStringUtil.equalsIgnoreCases(value, offset, rhs.value, rhs.offset, length); } byte[] value = this.value; diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 4d4567523d8..5ca4171a2e9 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -15,6 +15,7 @@ package io.netty.util; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SWARUtil; /** * A collection of utility methods that is related with handling {@link AsciiString} @@ -31,12 +32,12 @@ static int firstIndexOf(final byte[] bytes, int fromIndex, final int toIndex, fi } final int length = toIndex - fromIndex; final int longCount = length >>> 3; - final long pattern = SWARByteUtil.compilePattern(value); + final long pattern = SWARUtil.compilePattern(value); for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(bytes, fromIndex); - final long mask = SWARByteUtil.applyPattern(word, pattern); + final long mask = SWARUtil.applyPattern(word, pattern); if (mask != 0) { - return fromIndex + SWARByteUtil.getIndex(mask, PlatformDependent.BIG_ENDIAN_NATIVE_ORDER); + return fromIndex + SWARUtil.getIndex(mask, PlatformDependent.BIG_ENDIAN_NATIVE_ORDER); } fromIndex += Long.BYTES; } @@ -145,7 +146,7 @@ static boolean containsUpperCase(final byte[] bytes, int fromIndex, final int to final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(bytes, fromIndex); - if (SWARByteUtil.containsUpperCase(word)) { + if (SWARUtil.containsUpperCase(word)) { return true; } fromIndex += Long.BYTES; @@ -166,7 +167,7 @@ private static boolean linearContainsUpperCase(final byte[] bytes, final int fro private static boolean unrolledConstainsUpperCase(final byte[] bytes, int fromIndex, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); - if (SWARByteUtil.containsUpperCase(word)) { + if (SWARUtil.containsUpperCase(word)) { return true; } fromIndex += Integer.BYTES; @@ -199,7 +200,7 @@ static boolean containsLowerCase(final byte[] bytes, int fromIndex, final int to final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(bytes, fromIndex); - if (SWARByteUtil.containsLowerCase(word)) { + if (SWARUtil.containsLowerCase(word)) { return true; } fromIndex += Long.BYTES; @@ -219,7 +220,7 @@ private static boolean linearContainsLowerCase(final byte[] bytes, final int fro private static boolean unrolledContainsLowerCase(final byte[] bytes, int fromIndex, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(bytes, fromIndex); - if (SWARByteUtil.containsLowerCase(word)) { + if (SWARUtil.containsLowerCase(word)) { return true; } fromIndex += Integer.BYTES; @@ -253,7 +254,7 @@ static void toLowerCase(final byte[] src, int srcPos, final byte[] dest, int des final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(src, srcPos); - PlatformDependent.putLong(dest, destPos, SWARByteUtil.toLowerCase(word)); + PlatformDependent.putLong(dest, destPos, SWARUtil.toLowerCase(word)); srcPos += Long.BYTES; destPos += Long.BYTES; } @@ -271,7 +272,7 @@ private static void unrollToLowerCase(final byte[] src, int srcPos, final byte[] dest, int destPos, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); - PlatformDependent.putInt(dest, destPos, SWARByteUtil.toLowerCase(word)); + PlatformDependent.putInt(dest, destPos, SWARUtil.toLowerCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } @@ -302,7 +303,7 @@ static void toUpperCase(final byte[] src, int srcPos, final byte[] dest, int des final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { final long word = PlatformDependent.getLong(src, srcPos); - PlatformDependent.putLong(dest, destPos, SWARByteUtil.toUpperCase(word)); + PlatformDependent.putLong(dest, destPos, SWARUtil.toUpperCase(word)); srcPos += Long.BYTES; destPos += Long.BYTES; } @@ -322,7 +323,7 @@ private static void unrolltoUpperCase(final byte[] src, int srcPos, final byte[] dest, int destPos, final int length) { if ((length & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); - PlatformDependent.putInt(dest, destPos, SWARByteUtil.toUpperCase(word)); + PlatformDependent.putInt(dest, destPos, SWARUtil.toUpperCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } @@ -359,7 +360,7 @@ static boolean equalsIgnoreCases(final byte[] lhs, int lhsPos, final byte[] rhs, for (int i = 0; i < longCount; ++i) { final long lWord = PlatformDependent.getLong(lhs, lhsPos); final long rWord = PlatformDependent.getLong(rhs, rhsPos); - if (SWARByteUtil.toLowerCase(lWord) != SWARByteUtil.toLowerCase(rWord)) { + if (SWARUtil.toLowerCase(lWord) != SWARUtil.toLowerCase(rWord)) { return false; } lhsPos += Long.BYTES; @@ -385,7 +386,7 @@ private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, if ((length & 4) != 0) { final int lWord = PlatformDependent.getInt(lhs, lhsPos); final int rWord = PlatformDependent.getInt(rhs, rhsPos); - if (SWARByteUtil.toLowerCase(lWord) != SWARByteUtil.toLowerCase(rWord)) { + if (SWARUtil.toLowerCase(lWord) != SWARUtil.toLowerCase(rWord)) { return false; } lhsPos += Integer.BYTES; @@ -410,169 +411,6 @@ private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, return true; } - /** - * A collection methods for performing operations using SWAR (SIMD within a register) techniques. - */ - public static final class SWARByteUtil { - - /** - * Compiles given byte into a long pattern suitable for SWAR operations. - */ - public static long compilePattern(final byte byteToFind) { - return (byteToFind & 0xFFL) * 0x101010101010101L; - } - - /** - * Returns the index of the first occurrence of the specified pattern in the specified word. - * If no pattern is found, returns 8. - */ - public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { - long tmp = applyPattern(word, pattern); - final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; - } - - /** - * Applies a compiled pattern to given word. - * Returns a word where each byte that matches the pattern has the highest bit set. - */ - private static long applyPattern(final long word, final long pattern) { - long input = word ^ pattern; - long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; - return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); - } - - /** - * Returns the index of the first occurrence of byte that specificied in the pattern. - * If no pattern is found, returns 8. - * - * @param word the return value of {@link #applyPattern(long, long)} - * @param isNative if ture, if given word is big endian - * if false, if given word is little endian - * @return the index of the first occurrence of the specified pattern in the specified word. - * If no pattern is found, returns 8. - */ - public static int getIndex(final long word, final boolean isNative) { - final int tmp = isNative? Long.numberOfLeadingZeros(word) : Long.numberOfTrailingZeros(word); - return tmp >>> 3; - } - - /** - * Returns a word where each ASCII uppercase byte has the highest bit set. - */ - private static long applyUpperCasePattern(final long word) { - long rotated = word & 0x7F7F7F7F7F7F7F7FL; - rotated += 0x2525252525252525L; - rotated &= 0x7F7F7F7F7F7F7F7FL; - rotated += 0x1A1A1A1A1A1A1A1AL; - rotated &= ~word; - rotated &= 0x8080808080808080L; - return rotated; - } - - /** - * Returns a word where each ASCII uppercase byte has the highest bit set. - */ - private static int applyUpperCasePattern(final int word) { - int rotated = word & 0x7F7F7F7F; - rotated += 0x25252525; - rotated &= 0x7F7F7F7F; - rotated += 0x1A1A1A1A; - rotated &= ~word; - rotated &= 0x80808080; - return rotated; - } - - /** - * Returns a word where each ASCII lowercase byte has the highest bit set. - */ - private static long applyLowerCasePattern(final long word) { - long rotated = word & 0x7F7F7F7F7F7F7F7FL; - rotated += 0x0505050505050505L; - rotated &= 0x7F7F7F7F7F7F7F7FL; - rotated += 0x1A1A1A1A1A1A1A1AL; - rotated &= ~word; - rotated &= 0x8080808080808080L; - return rotated; - } - - /** - * Returns a word where each lowercase ASCII byte has the highest bit set. - */ - private static int applyLowerCasePattern(final int word) { - int rotated = word & 0x7F7F7F7F; - rotated += 0x05050505; - rotated &= 0x7F7F7F7F; - rotated += 0x1A1A1A1A; - rotated &= ~word; - rotated &= 0x80808080; - return rotated; - } - - /** - * Returns true if the given word contains at least one ASCII uppercase byte. - */ - static boolean containsUpperCase(final long word) { - return applyUpperCasePattern(word) != 0; - } - - /** - * Returns true if the given word contains at least one ASCII uppercase byte. - */ - static boolean containsUpperCase(final int word) { - return applyUpperCasePattern(word) != 0; - } - - /** - * Returns true if the given word contains at least one ASCII lowercase byte. - */ - static boolean containsLowerCase(final long word) { - return applyLowerCasePattern(word) != 0; - } - - /** - * Returns true if the given word contains at least one ASCII lowercase byte. - */ - static boolean containsLowerCase(final int word) { - return applyLowerCasePattern(word) != 0; - } - - /** - * Returns a word with all bytes converted to lowercase ASCII. - */ - static long toLowerCase(final long word) { - final long mask = applyUpperCasePattern(word) >>> 2; - return word | mask; - } - - /** - * Returns a word with all bytes converted to lowercase ASCII. - */ - static int toLowerCase(final int word) { - final int mask = applyUpperCasePattern(word) >>> 2; - return word | mask; - } - - /** - * Returns a word with all bytes converted to uppercase ASCII. - */ - static long toUpperCase(final long word) { - final long mask = applyLowerCasePattern(word) >>> 2; - return word & ~mask; - } - - /** - * Returns a word with all bytes converted to uppercase ASCII. - */ - static int toUpperCase(final int word) { - final int mask = applyLowerCasePattern(word) >>> 2; - return word & ~mask; - } - - private SWARByteUtil() { - } - } - private AsciiStringUtil() { } diff --git a/common/src/main/java/io/netty/util/internal/SWARUtil.java b/common/src/main/java/io/netty/util/internal/SWARUtil.java new file mode 100644 index 00000000000..2587e75ff97 --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/SWARUtil.java @@ -0,0 +1,164 @@ +package io.netty.util.internal; + +/** + * A collection methods for performing operations using SWAR (SIMD within a register) techniques. + */ +public final class SWARUtil { + + /** + * Compiles given byte into a long pattern suitable for SWAR operations. + */ + public static long compilePattern(final byte byteToFind) { + return (byteToFind & 0xFFL) * 0x101010101010101L; + } + + /** + * Returns the index of the first occurrence of the specified pattern in the specified word. + * If no pattern is found, returns 8. + */ + public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { + long tmp = applyPattern(word, pattern); + final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); + return binaryPosition >>> 3; + } + + /** + * Applies a compiled pattern to given word. + * Returns a word where each byte that matches the pattern has the highest bit set. + */ + public static long applyPattern(final long word, final long pattern) { + long input = word ^ pattern; + long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; + return ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); + } + + /** + * Returns the index of the first occurrence of byte that specificied in the pattern. + * If no pattern is found, returns 8. + * + * @param word the return value of {@link #applyPattern(long, long)} + * @param isNative if ture, if given word is big endian + * if false, if given word is little endian + * @return the index of the first occurrence of the specified pattern in the specified word. + * If no pattern is found, returns 8. + */ + public static int getIndex(final long word, final boolean isNative) { + final int tmp = isNative? Long.numberOfLeadingZeros(word) : Long.numberOfTrailingZeros(word); + return tmp >>> 3; + } + + /** + * Returns a word where each ASCII uppercase byte has the highest bit set. + */ + private static long applyUpperCasePattern(final long word) { + long rotated = word & 0x7F7F7F7F7F7F7F7FL; + rotated += 0x2525252525252525L; + rotated &= 0x7F7F7F7F7F7F7F7FL; + rotated += 0x1A1A1A1A1A1A1A1AL; + rotated &= ~word; + rotated &= 0x8080808080808080L; + return rotated; + } + + /** + * Returns a word where each ASCII uppercase byte has the highest bit set. + */ + private static int applyUpperCasePattern(final int word) { + int rotated = word & 0x7F7F7F7F; + rotated += 0x25252525; + rotated &= 0x7F7F7F7F; + rotated += 0x1A1A1A1A; + rotated &= ~word; + rotated &= 0x80808080; + return rotated; + } + + /** + * Returns a word where each ASCII lowercase byte has the highest bit set. + */ + private static long applyLowerCasePattern(final long word) { + long rotated = word & 0x7F7F7F7F7F7F7F7FL; + rotated += 0x0505050505050505L; + rotated &= 0x7F7F7F7F7F7F7F7FL; + rotated += 0x1A1A1A1A1A1A1A1AL; + rotated &= ~word; + rotated &= 0x8080808080808080L; + return rotated; + } + + /** + * Returns a word where each lowercase ASCII byte has the highest bit set. + */ + private static int applyLowerCasePattern(final int word) { + int rotated = word & 0x7F7F7F7F; + rotated += 0x05050505; + rotated &= 0x7F7F7F7F; + rotated += 0x1A1A1A1A; + rotated &= ~word; + rotated &= 0x80808080; + return rotated; + } + + /** + * Returns true if the given word contains at least one ASCII uppercase byte. + */ + public static boolean containsUpperCase(final long word) { + return applyUpperCasePattern(word) != 0; + } + + /** + * Returns true if the given word contains at least one ASCII uppercase byte. + */ + public static boolean containsUpperCase(final int word) { + return applyUpperCasePattern(word) != 0; + } + + /** + * Returns true if the given word contains at least one ASCII lowercase byte. + */ + public static boolean containsLowerCase(final long word) { + return applyLowerCasePattern(word) != 0; + } + + /** + * Returns true if the given word contains at least one ASCII lowercase byte. + */ + public static boolean containsLowerCase(final int word) { + return applyLowerCasePattern(word) != 0; + } + + /** + * Returns a word with all bytes converted to lowercase ASCII. + */ + public static long toLowerCase(final long word) { + final long mask = applyUpperCasePattern(word) >>> 2; + return word | mask; + } + + /** + * Returns a word with all bytes converted to lowercase ASCII. + */ + public static int toLowerCase(final int word) { + final int mask = applyUpperCasePattern(word) >>> 2; + return word | mask; + } + + /** + * Returns a word with all bytes converted to uppercase ASCII. + */ + public static long toUpperCase(final long word) { + final long mask = applyLowerCasePattern(word) >>> 2; + return word & ~mask; + } + + /** + * Returns a word with all bytes converted to uppercase ASCII. + */ + public static int toUpperCase(final int word) { + final int mask = applyLowerCasePattern(word) >>> 2; + return word & ~mask; + } + + private SWARUtil() { + } +} diff --git a/common/src/test/java/io/netty/util/AsciiStringUtilTest.java b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java index f0cb55ee19c..367eaef74b8 100644 --- a/common/src/test/java/io/netty/util/AsciiStringUtilTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringUtilTest.java @@ -14,6 +14,7 @@ */ package io.netty.util; +import io.netty.util.internal.SWARUtil; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,7 +35,7 @@ public void toLowerCaseTest() { // test 4 bytes for (int i = 0; i < eAsciiTable.length; i += 4) { final int word = getInt(eAsciiTable, i); - final int converted = AsciiStringUtil.SWARByteUtil.toLowerCase(word); + final int converted = SWARUtil.toLowerCase(word); for (int j = 0; j < Integer.BYTES; ++j) { final byte expected = AsciiStringUtil.toLowerCase(eAsciiTable[i + j]); final byte actual = getByte(converted, j); @@ -45,7 +46,7 @@ public void toLowerCaseTest() { // test 8 bytes for (int i = 0; i < eAsciiTable.length; i += 8) { final long word = getLong(eAsciiTable, i); - final long converted = AsciiStringUtil.SWARByteUtil.toLowerCase(word); + final long converted = SWARUtil.toLowerCase(word); for (int j = 0; j < Long.BYTES; ++j) { final byte expected = AsciiStringUtil.toLowerCase(eAsciiTable[i + j]); final byte actual = getByte(converted, j); @@ -68,7 +69,7 @@ public void toUpperCaseTest() { // test 4 bytes for (int i = 0; i < eAsciiTable.length; i += 4) { final int word = getInt(eAsciiTable, i); - final int converted = AsciiStringUtil.SWARByteUtil.toUpperCase(word); + final int converted = SWARUtil.toUpperCase(word); for (int j = 0; j < Integer.BYTES; ++j) { final byte expected = AsciiStringUtil.toUpperCase(eAsciiTable[i + j]); final byte actual = getByte(converted, j); @@ -79,7 +80,7 @@ public void toUpperCaseTest() { // test 8 bytes for (int i = 0; i < eAsciiTable.length; i += 8) { final long word = getLong(eAsciiTable, i); - final long converted = AsciiStringUtil.SWARByteUtil.toUpperCase(word); + final long converted = SWARUtil.toUpperCase(word); for (int j = 0; j < Long.BYTES; ++j) { final byte expected = AsciiStringUtil.toUpperCase(eAsciiTable[i + j]); final byte actual = getByte(converted, j); From ec5a2100efd97c18f382add9b7dd5548d46f864f Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 17 Mar 2024 22:38:22 +0900 Subject: [PATCH 20/24] align terms. --- .../main/java/io/netty/util/AsciiString.java | 4 +- .../java/io/netty/util/AsciiStringUtil.java | 74 ++++++++++--------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index b742f03934f..3985f9ee628 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -921,7 +921,7 @@ public boolean startsWith(CharSequence prefix, int start) { * @return a new string containing the lowercase characters equivalent to the characters in this string. */ public AsciiString toLowerCase() { - if (!AsciiStringUtil.containsUpperCase(value, offset, offset + length)) { + if (!AsciiStringUtil.containsUpperCase(value, offset, length)) { return this; } @@ -936,7 +936,7 @@ public AsciiString toLowerCase() { * @return a new string containing the uppercase characters equivalent to the characters in this string. */ public AsciiString toUpperCase() { - if (!AsciiStringUtil.containsLowerCase(value, offset, offset + length)) { + if (!AsciiStringUtil.containsLowerCase(value, offset, length)) { return this; } final byte[] newValue = PlatformDependent.allocateUninitializedArray(length); diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 5ca4171a2e9..fa09dfd4531 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -137,26 +137,26 @@ static byte toLowerCase(final byte value) { * Returns true if the given byte array contains at least one upper case character. false otherwise. * This method utilizes SWAR technique to accelerate containsUpperCase operation. */ - static boolean containsUpperCase(final byte[] bytes, int fromIndex, final int toIndex) { + static boolean containsUpperCase(final byte[] bytes, int index, final int length) { if (!PlatformDependent.isUnaligned()) { - return linearContainsUpperCase(bytes, fromIndex, toIndex); + return linearContainsUpperCase(bytes, index, length); } - final int length = toIndex - fromIndex; final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { - final long word = PlatformDependent.getLong(bytes, fromIndex); + final long word = PlatformDependent.getLong(bytes, index); if (SWARUtil.containsUpperCase(word)) { return true; } - fromIndex += Long.BYTES; + index += Long.BYTES; } final int byteCount = length & 7; - return unrolledConstainsUpperCase(bytes, fromIndex, byteCount); + return unrolledConstainsUpperCase(bytes, index, byteCount); } - private static boolean linearContainsUpperCase(final byte[] bytes, final int fromIndex, final int toIndex) { - for (int idx = fromIndex; idx < toIndex; ++idx) { + private static boolean linearContainsUpperCase(final byte[] bytes, final int index, final int length) { + final int end = index + length; + for (int idx = index; idx < end; ++idx) { if (isUpperCase(bytes[idx])) { return true; } @@ -164,25 +164,26 @@ private static boolean linearContainsUpperCase(final byte[] bytes, final int fro return false; } - private static boolean unrolledConstainsUpperCase(final byte[] bytes, int fromIndex, final int length) { - if ((length & 4) != 0) { - final int word = PlatformDependent.getInt(bytes, fromIndex); + private static boolean unrolledConstainsUpperCase(final byte[] bytes, int index, final int byteCount) { + assert byteCount < 8; + if ((byteCount & 4) != 0) { + final int word = PlatformDependent.getInt(bytes, index); if (SWARUtil.containsUpperCase(word)) { return true; } - fromIndex += Integer.BYTES; + index += Integer.BYTES; } - if ((length & 2) != 0) { - if (isUpperCase(PlatformDependent.getByte(bytes, fromIndex))) { + if ((byteCount & 2) != 0) { + if (isUpperCase(PlatformDependent.getByte(bytes, index))) { return true; } - if (isUpperCase(PlatformDependent.getByte(bytes, fromIndex + 1))) { + if (isUpperCase(PlatformDependent.getByte(bytes, index + 1))) { return true; } - fromIndex += 2; + index += 2; } - if ((length & 1) != 0) { - return isUpperCase(PlatformDependent.getByte(bytes, fromIndex)); + if ((byteCount & 1) != 0) { + return isUpperCase(PlatformDependent.getByte(bytes, index)); } return false; } @@ -191,25 +192,25 @@ private static boolean unrolledConstainsUpperCase(final byte[] bytes, int fromIn * Returns true if the given byte array contains at least one lower case character. false otherwise. * This method utilizes SWAR technique to accelerate containsLowerCase operation. */ - static boolean containsLowerCase(final byte[] bytes, int fromIndex, final int toIndex) { + static boolean containsLowerCase(final byte[] bytes, int index, final int length) { if (!PlatformDependent.isUnaligned()) { - return linearContainsLowerCase(bytes, fromIndex, toIndex); + return linearContainsLowerCase(bytes, index, length); } - final int length = toIndex - fromIndex; final int longCount = length >>> 3; for (int i = 0; i < longCount; ++i) { - final long word = PlatformDependent.getLong(bytes, fromIndex); + final long word = PlatformDependent.getLong(bytes, index); if (SWARUtil.containsLowerCase(word)) { return true; } - fromIndex += Long.BYTES; + index += Long.BYTES; } - return unrolledContainsLowerCase(bytes, fromIndex, length & 7); + return unrolledContainsLowerCase(bytes, index, length & 7); } - private static boolean linearContainsLowerCase(final byte[] bytes, final int fromIndex, final int toIndex) { - for (int idx = fromIndex; idx < toIndex; ++idx) { + private static boolean linearContainsLowerCase(final byte[] bytes, final int index, final int length) { + final int end = index + length; + for (int idx = index; idx < end; ++idx) { if (isLowerCase(bytes[idx])) { return true; } @@ -217,25 +218,26 @@ private static boolean linearContainsLowerCase(final byte[] bytes, final int fro return false; } - private static boolean unrolledContainsLowerCase(final byte[] bytes, int fromIndex, final int length) { - if ((length & 4) != 0) { - final int word = PlatformDependent.getInt(bytes, fromIndex); + private static boolean unrolledContainsLowerCase(final byte[] bytes, int index, final int byteCount) { + assert byteCount < 8; + if ((byteCount & 4) != 0) { + final int word = PlatformDependent.getInt(bytes, index); if (SWARUtil.containsLowerCase(word)) { return true; } - fromIndex += Integer.BYTES; + index += Integer.BYTES; } - if ((length & 2) != 0) { - if (isLowerCase(PlatformDependent.getByte(bytes, fromIndex))) { + if ((byteCount & 2) != 0) { + if (isLowerCase(PlatformDependent.getByte(bytes, index))) { return true; } - if (isLowerCase(PlatformDependent.getByte(bytes, fromIndex + 1))) { + if (isLowerCase(PlatformDependent.getByte(bytes, index + 1))) { return true; } - fromIndex += 2; + index += 2; } - if ((length & 1) != 0) { - return isLowerCase(PlatformDependent.getByte(bytes, fromIndex)); + if ((byteCount & 1) != 0) { + return isLowerCase(PlatformDependent.getByte(bytes, index)); } return false; } From 0c4f6220181a557b331a55c8a0e1af8be5851bfd Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 17 Mar 2024 22:47:17 +0900 Subject: [PATCH 21/24] add copyright header --- .../java/io/netty/util/internal/SWARUtil.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/src/main/java/io/netty/util/internal/SWARUtil.java b/common/src/main/java/io/netty/util/internal/SWARUtil.java index 2587e75ff97..32709d36913 100644 --- a/common/src/main/java/io/netty/util/internal/SWARUtil.java +++ b/common/src/main/java/io/netty/util/internal/SWARUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ package io.netty.util.internal; /** From 71d486af78a8cce1713e3dcc6236b9c6a6c3a4f8 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 17 Mar 2024 22:54:40 +0900 Subject: [PATCH 22/24] make AsciiStringUtil package private --- common/src/main/java/io/netty/util/AsciiStringUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index fa09dfd4531..26f98aaed83 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -20,7 +20,7 @@ /** * A collection of utility methods that is related with handling {@link AsciiString} */ -public final class AsciiStringUtil { +final class AsciiStringUtil { /** * Returns index of the first occurrence of the specified byte in the specified array. From f9c626683d26827f85738c02898b705e52e5c9ac Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 17 Mar 2024 23:38:39 +0900 Subject: [PATCH 23/24] Speed up ByteBufUtil#firstIndexOf --- buffer/src/main/java/io/netty/buffer/ByteBufUtil.java | 6 +++--- .../src/main/java/io/netty/util/internal/SWARUtil.java | 10 ---------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index 13845c26c4b..10d49fd502f 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -594,9 +594,9 @@ static int firstIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte for (int i = 0; i < longCount; i++) { // use the faster available getLong final long word = useLE? buffer._getLongLE(offset) : buffer._getLong(offset); - int index = SWARUtil.firstAnyPattern(word, pattern, isNative); - if (index < Long.BYTES) { - return offset + index; + final long mask = SWARUtil.applyPattern(word, pattern); + if (mask != 0) { + return offset + SWARUtil.getIndex(mask, isNative); } offset += Long.BYTES; } diff --git a/common/src/main/java/io/netty/util/internal/SWARUtil.java b/common/src/main/java/io/netty/util/internal/SWARUtil.java index 32709d36913..6f646a5cc84 100644 --- a/common/src/main/java/io/netty/util/internal/SWARUtil.java +++ b/common/src/main/java/io/netty/util/internal/SWARUtil.java @@ -27,16 +27,6 @@ public static long compilePattern(final byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } - /** - * Returns the index of the first occurrence of the specified pattern in the specified word. - * If no pattern is found, returns 8. - */ - public static int firstAnyPattern(final long word, final long pattern, final boolean leading) { - long tmp = applyPattern(word, pattern); - final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); - return binaryPosition >>> 3; - } - /** * Applies a compiled pattern to given word. * Returns a word where each byte that matches the pattern has the highest bit set. From a9d041f52b72fa61100ad3a7efa6ebad1fdf8fbc Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 18 Mar 2024 00:23:08 +0900 Subject: [PATCH 24/24] fix naming --- .../java/io/netty/util/AsciiStringUtil.java | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/common/src/main/java/io/netty/util/AsciiStringUtil.java b/common/src/main/java/io/netty/util/AsciiStringUtil.java index 26f98aaed83..efe13d27c63 100644 --- a/common/src/main/java/io/netty/util/AsciiStringUtil.java +++ b/common/src/main/java/io/netty/util/AsciiStringUtil.java @@ -41,8 +41,7 @@ static int firstIndexOf(final byte[] bytes, int fromIndex, final int toIndex, fi } fromIndex += Long.BYTES; } - final int byteCount = length & 7; - return unrolledFirstIndexOf(bytes, fromIndex, byteCount, value); + return unrolledFirstIndexOf(bytes, fromIndex, length & 7, value); } private static int linearFirstIndexOf(final byte[] bytes, final int fromIndex, @@ -150,8 +149,7 @@ static boolean containsUpperCase(final byte[] bytes, int index, final int length } index += Long.BYTES; } - final int byteCount = length & 7; - return unrolledConstainsUpperCase(bytes, index, byteCount); + return unrolledConstainsUpperCase(bytes, index, length & 7); } private static boolean linearContainsUpperCase(final byte[] bytes, final int index, final int length) { @@ -165,7 +163,7 @@ private static boolean linearContainsUpperCase(final byte[] bytes, final int ind } private static boolean unrolledConstainsUpperCase(final byte[] bytes, int index, final int byteCount) { - assert byteCount < 8; + assert byteCount >= 0 && byteCount < 8; if ((byteCount & 4) != 0) { final int word = PlatformDependent.getInt(bytes, index); if (SWARUtil.containsUpperCase(word)) { @@ -219,7 +217,7 @@ private static boolean linearContainsLowerCase(final byte[] bytes, final int ind } private static boolean unrolledContainsLowerCase(final byte[] bytes, int index, final int byteCount) { - assert byteCount < 8; + assert byteCount >= 0 && byteCount < 8; if ((byteCount & 4) != 0) { final int word = PlatformDependent.getInt(bytes, index); if (SWARUtil.containsLowerCase(word)) { @@ -260,7 +258,7 @@ static void toLowerCase(final byte[] src, int srcPos, final byte[] dest, int des srcPos += Long.BYTES; destPos += Long.BYTES; } - unrollToLowerCase(src, srcPos, dest, destPos, length & 7); + unrolledToLowerCase(src, srcPos, dest, destPos, length & 7); } private static void linearToLowerCase(final byte[] src, int srcPos, @@ -270,15 +268,16 @@ private static void linearToLowerCase(final byte[] src, int srcPos, } } - private static void unrollToLowerCase(final byte[] src, int srcPos, - final byte[] dest, int destPos, final int length) { - if ((length & 4) != 0) { + private static void unrolledToLowerCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int byteCount) { + assert byteCount >= 0 && byteCount < 8; + if ((byteCount & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); PlatformDependent.putInt(dest, destPos, SWARUtil.toLowerCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } - if ((length & 2) != 0) { + if ((byteCount & 2) != 0) { PlatformDependent.putByte(dest, destPos, toLowerCase(PlatformDependent.getByte(src, srcPos))); PlatformDependent.putByte(dest, destPos + 1, @@ -286,7 +285,7 @@ private static void unrollToLowerCase(final byte[] src, int srcPos, srcPos += 2; destPos += 2; } - if ((length & 1) != 0) { + if ((byteCount & 1) != 0) { PlatformDependent.putByte(dest, destPos, toLowerCase(PlatformDependent.getByte(src, srcPos))); } @@ -310,8 +309,7 @@ static void toUpperCase(final byte[] src, int srcPos, final byte[] dest, int des destPos += Long.BYTES; } - final int byteCount = length & 7; - unrolltoUpperCase(src, srcPos, dest, destPos, byteCount); + unrolledtoUpperCase(src, srcPos, dest, destPos, length & 7); } private static void linearToUpperCase(final byte[] src, int srcPos, @@ -321,15 +319,16 @@ private static void linearToUpperCase(final byte[] src, int srcPos, } } - private static void unrolltoUpperCase(final byte[] src, int srcPos, - final byte[] dest, int destPos, final int length) { - if ((length & 4) != 0) { + private static void unrolledtoUpperCase(final byte[] src, int srcPos, + final byte[] dest, int destPos, final int byteCount) { + assert byteCount >= 0 && byteCount < 8; + if ((byteCount & 4) != 0) { final int word = PlatformDependent.getInt(src, srcPos); PlatformDependent.putInt(dest, destPos, SWARUtil.toUpperCase(word)); srcPos += Integer.BYTES; destPos += Integer.BYTES; } - if ((length & 2) != 0) { + if ((byteCount & 2) != 0) { PlatformDependent.putByte(dest, destPos, toUpperCase(PlatformDependent.getByte(src, srcPos))); PlatformDependent.putByte(dest, destPos + 1, @@ -337,7 +336,7 @@ private static void unrolltoUpperCase(final byte[] src, int srcPos, srcPos += 2; destPos += 2; } - if ((length & 1) != 0) { + if ((byteCount & 1) != 0) { PlatformDependent.putByte(dest, destPos, toUpperCase(PlatformDependent.getByte(src, srcPos))); } @@ -369,8 +368,7 @@ static boolean equalsIgnoreCases(final byte[] lhs, int lhsPos, final byte[] rhs, rhsPos += Long.BYTES; } } - final int byteCount = length & 7; - return unrollEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, byteCount); + return unrolledEqualsIgnoreCase(lhs, lhsPos, rhs, rhsPos, length & 7); } private static boolean linearEqualsIgnoreCase(final byte[] lhs, int lhsPos, @@ -383,9 +381,10 @@ private static boolean linearEqualsIgnoreCase(final byte[] lhs, int lhsPos, return true; } - private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, - final byte[] rhs, int rhsPos, final int length) { - if ((length & 4) != 0) { + private static boolean unrolledEqualsIgnoreCase(final byte[] lhs, int lhsPos, + final byte[] rhs, int rhsPos, final int byteCount) { + assert byteCount >= 0 && byteCount < 8; + if ((byteCount & 4) != 0) { final int lWord = PlatformDependent.getInt(lhs, lhsPos); final int rWord = PlatformDependent.getInt(rhs, rhsPos); if (SWARUtil.toLowerCase(lWord) != SWARUtil.toLowerCase(rWord)) { @@ -394,7 +393,7 @@ private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, lhsPos += Integer.BYTES; rhsPos += Integer.BYTES; } - if ((length & 2) != 0) { + if ((byteCount & 2) != 0) { if (toLowerCase(PlatformDependent.getByte(lhs, lhsPos)) != toLowerCase(PlatformDependent.getByte(rhs, rhsPos))) { return false; @@ -406,7 +405,7 @@ private static boolean unrollEqualsIgnoreCase(final byte[] lhs, int lhsPos, lhsPos += 2; rhsPos += 2; } - if ((length & 1) != 0) { + if ((byteCount & 1) != 0) { return toLowerCase(PlatformDependent.getByte(lhs, lhsPos)) == toLowerCase(PlatformDependent.getByte(rhs, rhsPos)); }