Improve performance of AsciiString.equals(Object).
Motivation: The current implementation does byte by byte comparison, which we have seen can be a performance bottleneck when the AsciiString is used as the key in a Map. Modifications: Use sun.misc.Unsafe (on supporting platforms) to compare up to eight bytes at a time and get closer to the performance of String.equals(Object). Result: Significant improvement (2x - 6x) in performance over the current implementation. Benchmark (size) Mode Samples Score Score error Units i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 10 thrpt 10 118843477.518 2347259.347 ops/s i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 50 thrpt 10 43910319.773 198376.996 ops/s i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 100 thrpt 10 26339969.001 159599.252 ops/s i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 1000 thrpt 10 2873119.030 20779.056 ops/s i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 10000 thrpt 10 306370.450 1933.303 ops/s i.n.m.i.PlatformDependentBenchmark.arraysBytesEqual 100000 thrpt 10 25750.415 108.391 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 10 thrpt 10 248077563.510 635320.093 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 50 thrpt 10 128198943.138 614827.548 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 100 thrpt 10 86195621.349 1063959.307 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 1000 thrpt 10 16920264.598 61615.365 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 10000 thrpt 10 1687454.747 6367.602 ops/s i.n.m.i.PlatformDependentBenchmark.unsafeBytesEqual 100000 thrpt 10 153717.851 586.916 ops/s
This commit is contained in:
parent
221a9f50d4
commit
c2de195f87
@ -15,6 +15,8 @@
|
||||
package io.netty.util;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@ -600,24 +602,12 @@ public class ByteString {
|
||||
if (!(obj instanceof ByteString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteString that = (ByteString) obj;
|
||||
if (length() != that.length() || hashCode() != that.hashCode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int end = value.length;
|
||||
for (int i = 0, j = 0; i < end; i++, j++) {
|
||||
if (value[i] != that.value[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
ByteString other = (ByteString) obj;
|
||||
return hashCode() == other.hashCode() &&
|
||||
PlatformDependent.equals(array(), 0, array().length, other.array(), 0, other.array().length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,7 +42,6 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Utility that detects various properties specific to the current runtime
|
||||
* environment, such as Java version and the availability of the
|
||||
@ -72,7 +71,7 @@ public final class PlatformDependent {
|
||||
HAS_UNSAFE && !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);
|
||||
private static final long MAX_DIRECT_MEMORY = maxDirectMemory0();
|
||||
|
||||
private static final long ARRAY_BASE_OFFSET = arrayBaseOffset0();
|
||||
private static final long ARRAY_BASE_OFFSET = PlatformDependent0.arrayBaseOffset();
|
||||
|
||||
private static final boolean HAS_JAVASSIST = hasJavassist0();
|
||||
|
||||
@ -350,6 +349,24 @@ public final class PlatformDependent {
|
||||
PlatformDependent0.copyMemory(null, srcAddr, dst, ARRAY_BASE_OFFSET + dstIndex, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two {@code byte} arrays for equality. For performance reasons no bounds checking on the
|
||||
* parameters is performed.
|
||||
*
|
||||
* @param bytes1 the first byte array.
|
||||
* @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
|
||||
* @param endPos1 the position (exclusive) to stop comparing in {@code bytes1}.
|
||||
* @param bytes2 the second byte array.
|
||||
* @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
|
||||
* @param endPos2 the position (exclusive) to stop comparing in {@code bytes2}.
|
||||
*/
|
||||
public static boolean equals(byte[] bytes1, int startPos1, int endPos1, byte[] bytes2, int startPos2, int endPos2) {
|
||||
if (!hasUnsafe() || !PlatformDependent0.unalignedAccess()) {
|
||||
return safeEquals(bytes1, startPos1, endPos1, bytes2, startPos2, endPos2);
|
||||
}
|
||||
return PlatformDependent0.equals(bytes1, startPos1, endPos1, bytes2, startPos2, endPos2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new optimized {@link AtomicReferenceFieldUpdater} or {@code null} if it
|
||||
* could not be created. Because of this the caller need to check for {@code null} and if {@code null} is returned
|
||||
@ -622,14 +639,6 @@ public final class PlatformDependent {
|
||||
}
|
||||
}
|
||||
|
||||
private static long arrayBaseOffset0() {
|
||||
if (!hasUnsafe()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return PlatformDependent0.arrayBaseOffset();
|
||||
}
|
||||
|
||||
private static long maxDirectMemory0() {
|
||||
long maxDirectMemory = 0;
|
||||
try {
|
||||
@ -847,6 +856,21 @@ public final class PlatformDependent {
|
||||
return PlatformDependent0.addressSize();
|
||||
}
|
||||
|
||||
private static boolean safeEquals(byte[] bytes1, int startPos1, int endPos1,
|
||||
byte[] bytes2, int startPos2, int endPos2) {
|
||||
final int len1 = endPos1 - startPos1;
|
||||
final int len2 = endPos2 - startPos2;
|
||||
if (len1 != len2) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len1; i++) {
|
||||
if (bytes1[startPos1 + i] != bytes2[startPos2 + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private PlatformDependent() {
|
||||
// only static method supported
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ final class PlatformDependent0 {
|
||||
private static final Unsafe UNSAFE;
|
||||
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
private static final long ADDRESS_FIELD_OFFSET;
|
||||
private static final long ARRAY_BASE_OFFSET;
|
||||
|
||||
/**
|
||||
* Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling
|
||||
@ -112,9 +113,11 @@ final class PlatformDependent0 {
|
||||
|
||||
if (unsafe == null) {
|
||||
ADDRESS_FIELD_OFFSET = -1;
|
||||
ARRAY_BASE_OFFSET = -1;
|
||||
UNALIGNED = false;
|
||||
} else {
|
||||
ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
|
||||
ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
|
||||
boolean unaligned;
|
||||
try {
|
||||
Class<?> bitsClass = Class.forName("java.nio.Bits", false, ClassLoader.getSystemClassLoader());
|
||||
@ -137,6 +140,10 @@ final class PlatformDependent0 {
|
||||
return UNSAFE != null;
|
||||
}
|
||||
|
||||
static boolean unalignedAccess() {
|
||||
return UNALIGNED;
|
||||
}
|
||||
|
||||
static void throwException(Throwable t) {
|
||||
UNSAFE.throwException(t);
|
||||
}
|
||||
@ -152,7 +159,7 @@ final class PlatformDependent0 {
|
||||
}
|
||||
|
||||
static long arrayBaseOffset() {
|
||||
return UNSAFE.arrayBaseOffset(byte[].class);
|
||||
return ARRAY_BASE_OFFSET;
|
||||
}
|
||||
|
||||
static Object getObject(Object object, long fieldOffset) {
|
||||
@ -311,6 +318,37 @@ final class PlatformDependent0 {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean equals(byte[] bytes1, int startPos1, int endPos1, byte[] bytes2, int startPos2, int endPos2) {
|
||||
final int len1 = endPos1 - startPos1;
|
||||
final int len2 = endPos2 - startPos2;
|
||||
if (len1 != len2) {
|
||||
return false;
|
||||
}
|
||||
if (len1 == 0) {
|
||||
return true;
|
||||
}
|
||||
final long baseOffset1 = ARRAY_BASE_OFFSET + startPos1;
|
||||
final long baseOffset2 = ARRAY_BASE_OFFSET + startPos2;
|
||||
int remainingBytes = len1 & 7;
|
||||
for (int i = len1 - 8; i >= remainingBytes; i -= 8) {
|
||||
if (UNSAFE.getLong(bytes1, baseOffset1 + i) != UNSAFE.getLong(bytes2, baseOffset2 + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (remainingBytes >= 4) {
|
||||
remainingBytes -= 4;
|
||||
if (UNSAFE.getInt(bytes1, baseOffset1 + remainingBytes) !=
|
||||
UNSAFE.getInt(bytes2, baseOffset2 + remainingBytes)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (remainingBytes >= 2) {
|
||||
return UNSAFE.getChar(bytes1, baseOffset1) == UNSAFE.getChar(bytes2, baseOffset2) &&
|
||||
(remainingBytes == 2 || bytes1[startPos1 + 2] == bytes2[startPos2 + 2]);
|
||||
}
|
||||
return bytes1[startPos1] == bytes2[startPos2];
|
||||
}
|
||||
|
||||
static <U, W> AtomicReferenceFieldUpdater<U, W> newAtomicReferenceFieldUpdater(
|
||||
Class<U> tclass, String fieldName) throws Exception {
|
||||
return new UnsafeAtomicReferenceFieldUpdater<U, W>(UNSAFE, tclass, fieldName);
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2015 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:
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PlatformDependentTest {
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
byte[] bytes1 = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
|
||||
byte[] bytes2 = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
|
||||
assertNotSame(bytes1, bytes2);
|
||||
assertTrue(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 2, bytes1.length, bytes2, 2, bytes2.length));
|
||||
|
||||
bytes1 = new byte[] {1, 2, 3, 4, 5, 6};
|
||||
bytes2 = new byte[] {1, 2, 3, 4, 5, 6, 7};
|
||||
assertNotSame(bytes1, bytes2);
|
||||
assertFalse(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes2, 0, 6, bytes1, 0, 6));
|
||||
|
||||
bytes1 = new byte[] {1, 2, 3, 4};
|
||||
bytes2 = new byte[] {1, 2, 3, 5};
|
||||
assertFalse(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 0, 3, bytes2, 0, 3));
|
||||
|
||||
bytes1 = new byte[] {1, 2, 3, 4};
|
||||
bytes2 = new byte[] {1, 3, 3, 4};
|
||||
assertFalse(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 2, bytes1.length, bytes2, 2, bytes2.length));
|
||||
|
||||
bytes1 = new byte[0];
|
||||
bytes2 = new byte[0];
|
||||
assertNotSame(bytes1, bytes2);
|
||||
assertTrue(PlatformDependent.equals(bytes1, 0, 0, bytes2, 0, 0));
|
||||
|
||||
bytes1 = new byte[100];
|
||||
bytes2 = new byte[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
bytes1[i] = (byte) i;
|
||||
bytes2[i] = (byte) i;
|
||||
}
|
||||
assertTrue(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
bytes1[50] = 0;
|
||||
assertFalse(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 51, bytes1.length, bytes2, 51, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 0, 50, bytes2, 0, 50));
|
||||
|
||||
bytes1 = new byte[]{1, 2, 3, 4, 5};
|
||||
bytes2 = new byte[]{3, 4, 5};
|
||||
assertFalse(PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes1, 2, bytes1.length, bytes2, 0, bytes2.length));
|
||||
assertTrue(PlatformDependent.equals(bytes2, 0, bytes2.length, bytes1, 2, bytes1.length));
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2015 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:
|
||||
*
|
||||
* http://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.microbench.internal;
|
||||
|
||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
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 java.util.Arrays;
|
||||
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
public class PlatformDependentBenchmark extends AbstractMicrobenchmark {
|
||||
|
||||
@Param({ "10", "50", "100", "1000", "10000", "100000" })
|
||||
private String size;
|
||||
private byte[] bytes1;
|
||||
private byte[] bytes2;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
int size = Integer.parseInt(this.size);
|
||||
bytes1 = new byte[size];
|
||||
bytes2 = new byte[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
bytes1[i] = (byte) i;
|
||||
bytes2[i] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public boolean unsafeBytesEqual() {
|
||||
return PlatformDependent.equals(bytes1, 0, bytes1.length, bytes2, 0, bytes2.length);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public boolean arraysBytesEqual() {
|
||||
return Arrays.equals(bytes1, bytes2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user