/* * Copyright (C) 2015 The Guava Authors * * Licensed 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 com.google.common.hash; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.security.MessageDigest; import java.util.Arrays; import java.util.Random; /** * Benchmarks for comparing the various {@link HashCode#equals} methods. * * <p>Parameters for the benchmark are: * <ul> * <li>size: the length of the byte array to hash * <li>whereToDiffer: where in the array the bytes should differ * <li>equalsImpl: which implementation of array equality to use * </ul> * * <p><b>Important note:</b> the primary goal of this benchmark is to ensure that varying {@code * whereToDiffer} produces no observable change in performance. We want to make sure that the * array equals implementation is *not* short-circuiting to prevent timing-based attacks. Being * fast is only a secondary goal. * * @author Kurt Alfred Kluever */ public class HashCodeBenchmark { // Use a statically configured random instance for all of the benchmarks private static final Random random = new Random(42); @Param({"1000", "100000"}) private int size; @Param WhereToDiffer whereToDiffer; @Param EqualsImplementation equalsImpl; private enum WhereToDiffer { ONE_PERCENT_IN, LAST_BYTE, NOT_AT_ALL; } private enum EqualsImplementation { ANDING_BOOLEANS { @Override boolean doEquals(byte[] a, byte[] b) { if (a.length != b.length) { return false; } boolean areEqual = true; for (int i = 0; i < a.length; i++) { areEqual &= (a[i] == b[i]); } return areEqual; } }, XORING_TO_BYTE { @Override boolean doEquals(byte[] a, byte[] b) { if (a.length != b.length) { return false; } byte result = 0; for (int i = 0; i < a.length; i++) { result = (byte) (result | a[i] ^ b[i]); } return (result == 0); } }, XORING_TO_INT { @Override boolean doEquals(byte[] a, byte[] b) { if (a.length != b.length) { return false; } int result = 0; for (int i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } return (result == 0); } }, MESSAGE_DIGEST_IS_EQUAL { @Override boolean doEquals(byte[] a, byte[] b) { return MessageDigest.isEqual(a, b); } }, ARRAYS_EQUALS { @Override boolean doEquals(byte[] a, byte[] b) { return Arrays.equals(a, b); } }; abstract boolean doEquals(byte[] a, byte[] b); } private byte[] testBytesA; private byte[] testBytesB; @BeforeExperiment void setUp() { testBytesA = new byte[size]; random.nextBytes(testBytesA); testBytesB = Arrays.copyOf(testBytesA, size); int indexToDifferAt = -1; switch (whereToDiffer) { case ONE_PERCENT_IN: indexToDifferAt = (int) (size * 0.01); break; case LAST_BYTE: indexToDifferAt = size - 1; break; case NOT_AT_ALL: } if (indexToDifferAt != -1) { testBytesA[indexToDifferAt] = (byte) (testBytesB[indexToDifferAt] - 1); } } @Benchmark boolean hashFunction(int reps) { boolean result = true; for (int i = 0; i < reps; i++) { result ^= equalsImpl.doEquals(testBytesA, testBytesB); } return result; } }