HpackUtil.equals performance improvement

Motivation:
PR #5355 modified interfaces to reduce GC related to the HPACK code. However this came with an anticipated performance regression related to HpackUtil.equals due to AsciiString's increase cost of charAt(..). We should mitigate this performance regression.

Modifications:
- Introduce an equals method in PlatformDependent which doesn't leak timing information and use this in HpcakUtil.equals

Result:
Fixes https://github.com/netty/netty/issues/5436
This commit is contained in:
Scott Mitchell 2016-06-22 10:37:35 -07:00
parent 2546d99864
commit 70651cc58d
9 changed files with 386 additions and 50 deletions

View File

@ -40,6 +40,7 @@ import java.util.Arrays;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.INCREMENTAL;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NEVER;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NONE;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.equalsConstantTime;
public final class Encoder {
@ -304,9 +305,8 @@ public final class Encoder {
int h = hash(name);
int i = index(h);
for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
if (e.hash == h &&
HpackUtil.equals(name, e.name) &&
HpackUtil.equals(value, e.value)) {
// To avoid short circuit behavior a bitwise operator is used instead of a boolean operator.
if (e.hash == h && (equalsConstantTime(name, e.name) & equalsConstantTime(value, e.value)) != 0) {
return e;
}
}
@ -325,7 +325,7 @@ public final class Encoder {
int i = index(h);
int index = -1;
for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
if (e.hash == h && HpackUtil.equals(name, e.name)) {
if (e.hash == h && equalsConstantTime(name, e.name) != 0) {
index = e.index;
break;
}

View File

@ -31,6 +31,7 @@
*/
package io.netty.handler.codec.http2.internal.hpack;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.equalsConstantTime;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
class HeaderField {
@ -72,9 +73,8 @@ class HeaderField {
return false;
}
HeaderField other = (HeaderField) obj;
boolean nameEquals = HpackUtil.equals(name, other.name);
boolean valueEquals = HpackUtil.equals(value, other.value);
return nameEquals && valueEquals;
// To avoid short circuit behavior a bitwise operator is used instead of a boolean operator.
return (equalsConstantTime(name, other.name) & equalsConstantTime(value, other.value)) != 0;
}
@Override

View File

@ -31,19 +31,38 @@
*/
package io.netty.handler.codec.http2.internal.hpack;
import io.netty.util.AsciiString;
import io.netty.util.internal.ConstantTimeUtils;
import io.netty.util.internal.PlatformDependent;
final class HpackUtil {
/**
* A string compare that doesn't leak timing information.
* Compare two {@link CharSequence} objects without leaking timing information.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* String s1 = "foo";
* String s2 = "foo";
* String s3 = "foo";
* String s4 = "goo";
* boolean equals = (equalsConstantTime(s1, s2) & equalsConstantTime(s3, s4)) != 0;
* </pre>
* @param s1 the first value.
* @param s2 the second value.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
static boolean equals(CharSequence s1, CharSequence s2) {
if (s1.length() != s2.length()) {
return false;
static int equalsConstantTime(CharSequence s1, CharSequence s2) {
if (s1 instanceof AsciiString && s2 instanceof AsciiString) {
if (s1.length() != s2.length()) {
return 0;
}
AsciiString s1Ascii = (AsciiString) s1;
AsciiString s2Ascii = (AsciiString) s2;
return PlatformDependent.equalsConstantTime(s1Ascii.array(), s1Ascii.arrayOffset(),
s2Ascii.array(), s2Ascii.arrayOffset(), s1.length());
}
char c = 0;
for (int i = 0; i < s1.length(); i++) {
c |= s1.charAt(i) ^ s2.charAt(i);
}
return c == 0;
return ConstantTimeUtils.equalsConstantTime(s1, s2);
}
// Section 6.2. Literal Header Field Representation

View File

@ -38,6 +38,8 @@ import io.netty.util.AsciiString;
import java.util.Arrays;
import java.util.List;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.equalsConstantTime;
final class StaticTable {
// Appendix A: Static Table
@ -153,10 +155,10 @@ final class StaticTable {
// Note this assumes all entries for a given header field are sequential.
while (index <= length) {
HeaderField entry = getEntry(index);
if (!HpackUtil.equals(name, entry.name)) {
if (equalsConstantTime(name, entry.name) == 0) {
break;
}
if (HpackUtil.equals(value, entry.value)) {
if (equalsConstantTime(value, entry.value) != 0) {
return index;
}
index++;

View File

@ -0,0 +1,131 @@
/*
* Copyright 2016 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;
public final class ConstantTimeUtils {
private ConstantTimeUtils() { }
/**
* Compare two {@code int}s without leaking timing information.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* int v1 = 1;
* int v1 = 1;
* int v1 = 1;
* int v1 = 500;
* boolean equals = (equalsConstantTime(l1, l2) & equalsConstantTime(l3, l4)) != 0;
* </pre>
* @param x the first value.
* @param y the second value.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
public static int equalsConstantTime(int x, int y) {
int z = -1 ^ (x ^ y);
z &= z >> 16;
z &= z >> 8;
z &= z >> 4;
z &= z >> 2;
z &= z >> 1;
return z & 1;
}
/**
* Compare two {@code longs}s without leaking timing information.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* long v1 = 1;
* long v1 = 1;
* long v1 = 1;
* long v1 = 500;
* boolean equals = (equalsConstantTime(l1, l2) & equalsConstantTime(l3, l4)) != 0;
* </pre>
* @param x the first value.
* @param y the second value.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
public static int equalsConstantTime(long x, long y) {
long z = -1L ^ (x ^ y);
z &= z >> 32;
z &= z >> 16;
z &= z >> 8;
z &= z >> 4;
z &= z >> 2;
z &= z >> 1;
return (int) (z & 1);
}
/**
* Compare two {@code byte} arrays for equality without leaking timing information.
* For performance reasons no bounds checking on the parameters is performed.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* byte[] s1 = new {1, 2, 3};
* byte[] s2 = new {1, 2, 3};
* byte[] s3 = new {1, 2, 3};
* byte[] s4 = new {4, 5, 6};
* boolean equals = (equalsConstantTime(s1, 0, s2, 0, s1.length) &
* equalsConstantTime(s3, 0, s4, 0, s3.length)) != 0;
* </pre>
* @param bytes1 the first byte array.
* @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
* @param bytes2 the second byte array.
* @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
* @param length the amount of bytes to compare. This is assumed to be validated as not going out of bounds
* by the caller.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
public static int equalsConstantTime(byte[] bytes1, int startPos1,
byte[] bytes2, int startPos2, int length) {
// Benchmarking demonstrates that using an int to accumulate is faster than other data types.
int b = 0;
final int end = startPos1 + length;
for (int i = startPos1, j = startPos2; i < end; ++i, ++j) {
b |= bytes1[i] ^ bytes2[j];
}
return equalsConstantTime(b, 0);
}
/**
* Compare two {@link CharSequence} objects without leaking timing information.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* String s1 = "foo";
* String s2 = "foo";
* String s3 = "foo";
* String s4 = "goo";
* boolean equals = (equalsConstantTime(s1, s2) & equalsConstantTime(s3, s4)) != 0;
* </pre>
* @param s1 the first value.
* @param s2 the second value.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
public static int equalsConstantTime(CharSequence s1, CharSequence s2) {
if (s1.length() != s2.length()) {
return 0;
}
// Benchmarking demonstrates that using an int to accumulate is faster than other data types.
int c = 0;
for (int i = 0; i < s1.length(); ++i) {
c |= s1.charAt(i) ^ s2.charAt(i);
}
return equalsConstantTime(c, 0);
}
}

View File

@ -59,6 +59,7 @@ import static io.netty.util.internal.PlatformDependent0.HASH_CODE_ASCII_SEED;
import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiCompute;
import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiSanitize;
import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiSanitizeAsByte;
import static io.netty.util.internal.PlatformDependent0.unalignedAccess;
/**
* Utility that detects various properties specific to the current runtime
@ -633,10 +634,36 @@ public final class PlatformDependent {
* by the caller.
*/
public static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
if (!hasUnsafe() || !PlatformDependent0.unalignedAccess()) {
return equalsSafe(bytes1, startPos1, bytes2, startPos2, length);
}
return PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
return !hasUnsafe() || !unalignedAccess() ?
equalsSafe(bytes1, startPos1, bytes2, startPos2, length) :
PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
}
/**
* Compare two {@code byte} arrays for equality without leaking timing information.
* For performance reasons no bounds checking on the parameters is performed.
* <p>
* The {@code int} return type is intentional and is designed to allow cascading of constant time operations:
* <pre>
* byte[] s1 = new {1, 2, 3};
* byte[] s2 = new {1, 2, 3};
* byte[] s3 = new {1, 2, 3};
* byte[] s4 = new {4, 5, 6};
* boolean equals = (equalsConstantTime(s1, 0, s2, 0, s1.length) &
* equalsConstantTime(s3, 0, s4, 0, s3.length)) != 0;
* </pre>
* @param bytes1 the first byte array.
* @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
* @param bytes2 the second byte array.
* @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
* @param length the amount of bytes to compare. This is assumed to be validated as not going out of bounds
* by the caller.
* @return {@code 0} if not equal. {@code 1} if equal.
*/
public static int equalsConstantTime(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
return !hasUnsafe() || !unalignedAccess() ?
ConstantTimeUtils.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length) :
PlatformDependent0.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length);
}
/**
@ -649,7 +676,7 @@ public final class PlatformDependent {
* The resulting hash code will be case insensitive.
*/
public static int hashCodeAscii(byte[] bytes, int startPos, int length) {
if (!hasUnsafe() || !PlatformDependent0.unalignedAccess()) {
if (!hasUnsafe() || !unalignedAccess()) {
return hashCodeAsciiSafe(bytes, startPos, length);
}
return PlatformDependent0.hashCodeAscii(bytes, startPos, length);
@ -666,7 +693,7 @@ public final class PlatformDependent {
* The resulting hash code will be case insensitive.
*/
public static int hashCodeAscii(CharSequence bytes) {
if (!hasUnsafe() || !PlatformDependent0.unalignedAccess()) {
if (!hasUnsafe() || !unalignedAccess()) {
return hashCodeAsciiSafe(bytes);
} else if (PlatformDependent0.hasCharArray(bytes)) {
return PlatformDependent0.hashCodeAscii(PlatformDependent0.charArray(bytes));

View File

@ -381,9 +381,6 @@ final class PlatformDependent0 {
}
static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
if (length == 0) {
return true;
}
final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
final long baseOffset2 = BYTE_ARRAY_BASE_OFFSET + startPos2;
final int remainingBytes = length & 7;
@ -418,6 +415,47 @@ final class PlatformDependent0 {
}
}
static int equalsConstantTime(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
long result = 0;
final long baseOffset1 = BYTE_ARRAY_BASE_OFFSET + startPos1;
final long baseOffset2 = BYTE_ARRAY_BASE_OFFSET + startPos2;
final int remainingBytes = length & 7;
final long end = baseOffset1 + remainingBytes;
for (long i = baseOffset1 - 8 + length, j = baseOffset2 - 8 + length; i >= end; i -= 8, j -= 8) {
result |= UNSAFE.getLong(bytes1, i) ^ UNSAFE.getLong(bytes2, j);
}
switch (remainingBytes) {
case 7:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getInt(bytes1, baseOffset1 + 3) ^ UNSAFE.getInt(bytes2, baseOffset2 + 3)) |
(UNSAFE.getChar(bytes1, baseOffset1 + 1) ^ UNSAFE.getChar(bytes2, baseOffset2 + 1)) |
(UNSAFE.getByte(bytes1, baseOffset1) ^ UNSAFE.getByte(bytes2, baseOffset2)), 0);
case 6:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getInt(bytes1, baseOffset1 + 2) ^ UNSAFE.getInt(bytes2, baseOffset2 + 2)) |
(UNSAFE.getChar(bytes1, baseOffset1) ^ UNSAFE.getChar(bytes2, baseOffset2)), 0);
case 5:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getInt(bytes1, baseOffset1 + 1) ^ UNSAFE.getInt(bytes2, baseOffset2 + 1)) |
(UNSAFE.getByte(bytes1, baseOffset1) ^ UNSAFE.getByte(bytes2, baseOffset2)), 0);
case 4:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getInt(bytes1, baseOffset1) ^ UNSAFE.getInt(bytes2, baseOffset2)), 0);
case 3:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getChar(bytes1, baseOffset1 + 1) ^ UNSAFE.getChar(bytes2, baseOffset2 + 1)) |
(UNSAFE.getByte(bytes1, baseOffset1) ^ UNSAFE.getByte(bytes2, baseOffset2)), 0);
case 2:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getChar(bytes1, baseOffset1) ^ UNSAFE.getChar(bytes2, baseOffset2)), 0);
case 1:
return ConstantTimeUtils.equalsConstantTime(result |
(UNSAFE.getByte(bytes1, baseOffset1) ^ UNSAFE.getByte(bytes2, baseOffset2)), 0);
default:
return ConstantTimeUtils.equalsConstantTime(result, 0);
}
}
static int hashCodeAscii(byte[] bytes) {
return hashCodeAscii(bytes, 0, bytes.length);
}

View File

@ -19,6 +19,8 @@ import org.junit.Test;
import java.util.Random;
import static io.netty.util.internal.PlatformDependent.hashCodeAscii;
import static io.netty.util.internal.PlatformDependent.hashCodeAsciiSafe;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@ -26,34 +28,57 @@ import static org.junit.Assert.assertTrue;
public class PlatformDependentTest {
private static final Random r = new Random();
@Test
public void testEqualsConsistentTime() {
testEquals(new EqualityChecker() {
@Override
public boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
return PlatformDependent.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length) != 0;
}
});
}
@Test
public void testEquals() {
testEquals(new EqualityChecker() {
@Override
public boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length) {
return PlatformDependent.equals(bytes1, startPos1, bytes2, startPos2, length);
}
});
}
private interface EqualityChecker {
boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2, int length);
}
private void testEquals(EqualityChecker equalsChecker) {
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, bytes2, 0, bytes1.length));
assertTrue(PlatformDependent.equals(bytes1, 2, bytes2, 2, bytes1.length - 2));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 2, bytes2, 2, bytes1.length - 2));
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, bytes2, 1, bytes1.length));
assertTrue(PlatformDependent.equals(bytes2, 0, bytes1, 0, bytes1.length));
assertFalse(equalsChecker.equals(bytes1, 0, bytes2, 1, bytes1.length));
assertTrue(equalsChecker.equals(bytes2, 0, bytes1, 0, bytes1.length));
bytes1 = new byte[] {1, 2, 3, 4};
bytes2 = new byte[] {1, 2, 3, 5};
assertFalse(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(PlatformDependent.equals(bytes1, 0, bytes2, 0, 3));
assertFalse(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, 3));
bytes1 = new byte[] {1, 2, 3, 4};
bytes2 = new byte[] {1, 3, 3, 4};
assertFalse(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(PlatformDependent.equals(bytes1, 2, bytes2, 2, bytes1.length - 2));
assertFalse(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 2, bytes2, 2, bytes1.length - 2));
bytes1 = new byte[0];
bytes2 = new byte[0];
assertNotSame(bytes1, bytes2);
assertTrue(PlatformDependent.equals(bytes1, 0, bytes2, 0, 0));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, 0));
bytes1 = new byte[100];
bytes2 = new byte[100];
@ -61,23 +86,23 @@ public class PlatformDependentTest {
bytes1[i] = (byte) i;
bytes2[i] = (byte) i;
}
assertTrue(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
bytes1[50] = 0;
assertFalse(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(PlatformDependent.equals(bytes1, 51, bytes2, 51, bytes1.length - 51));
assertTrue(PlatformDependent.equals(bytes1, 0, bytes2, 0, 50));
assertFalse(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 51, bytes2, 51, bytes1.length - 51));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, 50));
bytes1 = new byte[]{1, 2, 3, 4, 5};
bytes2 = new byte[]{3, 4, 5};
assertFalse(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes2.length));
assertTrue(PlatformDependent.equals(bytes1, 2, bytes2, 0, bytes2.length));
assertTrue(PlatformDependent.equals(bytes2, 0, bytes1, 2, bytes2.length));
assertFalse(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes2.length));
assertTrue(equalsChecker.equals(bytes1, 2, bytes2, 0, bytes2.length));
assertTrue(equalsChecker.equals(bytes2, 0, bytes1, 2, bytes2.length));
for (int i = 0; i < 1000; ++i) {
bytes1 = new byte[i];
r.nextBytes(bytes1);
bytes2 = bytes1.clone();
assertTrue(PlatformDependent.equals(bytes1, 0, bytes2, 0, bytes1.length));
assertTrue(equalsChecker.equals(bytes1, 0, bytes2, 0, bytes1.length));
}
}
@ -97,14 +122,14 @@ public class PlatformDependentTest {
}
String string = new String(bytesChar);
assertEquals("length=" + i,
PlatformDependent.hashCodeAsciiSafe(bytes, 0, bytes.length),
PlatformDependent.hashCodeAscii(bytes, 0, bytes.length));
hashCodeAsciiSafe(bytes, 0, bytes.length),
hashCodeAscii(bytes, 0, bytes.length));
assertEquals("length=" + i,
PlatformDependent.hashCodeAsciiSafe(string),
PlatformDependent.hashCodeAscii(string));
hashCodeAsciiSafe(string),
hashCodeAscii(string));
assertEquals("length=" + i,
PlatformDependent.hashCodeAscii(bytes, 0, bytes.length),
PlatformDependent.hashCodeAscii(string));
hashCodeAscii(bytes, 0, bytes.length),
hashCodeAscii(string));
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2016 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.http2.internal.hpack;
import io.netty.microbench.util.AbstractMicrobenchmark;
import io.netty.util.AsciiString;
import io.netty.util.internal.ConstantTimeUtils;
import io.netty.util.internal.PlatformDependent;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import java.util.List;
@Threads(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
public class HpackUtilBenchmark extends AbstractMicrobenchmark {
@Param
public HeadersSize size;
private List<Header> headers;
@Setup(Level.Trial)
public void setup() {
headers = Util.headers(size, false);
}
@Benchmark
public int oldEquals() {
int count = 0;
for (int i = 0; i < headers.size(); ++i) {
Header header = headers.get(i);
if (oldEquals(header.name, header.name)) {
++count;
}
}
return count;
}
@Benchmark
public int newEquals() {
int count = 0;
for (int i = 0; i < headers.size(); ++i) {
Header header = headers.get(i);
if (newEquals(header.name, header.name)) {
++count;
}
}
return count;
}
private static boolean oldEquals(CharSequence s1, CharSequence s2) {
if (s1.length() != s2.length()) {
return false;
}
char c = 0;
for (int i = 0; i < s1.length(); i++) {
c |= s1.charAt(i) ^ s2.charAt(i);
}
return c == 0;
}
private static boolean newEquals(CharSequence s1, CharSequence s2) {
if (s1 instanceof AsciiString && s2 instanceof AsciiString) {
if (s1.length() != s2.length()) {
return false;
}
AsciiString s1Ascii = (AsciiString) s1;
AsciiString s2Ascii = (AsciiString) s2;
return PlatformDependent.equalsConstantTime(s1Ascii.array(), s1Ascii.arrayOffset(),
s2Ascii.array(), s2Ascii.arrayOffset(), s1.length()) != 0;
}
return ConstantTimeUtils.equalsConstantTime(s1, s2) != 0;
}
}