1 /* 2 * Copyright (C) 2019 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import com.google.caliper.BeforeExperiment; 20 import com.google.caliper.Benchmark; 21 import com.google.caliper.Param; 22 import com.google.common.math.IntMath; 23 import java.math.RoundingMode; 24 25 /** Benchmark of implementations of {@link ImmutableSet#hashFloodingDetected(Object[])}. */ 26 public class ImmutableSetHashFloodingDetectionBenchmark { 27 private static final int TEST_CASES = 0x100; 28 29 @Param({"10", "100", "1000", "10000"}) 30 int size; 31 32 @Param Impl impl; 33 34 private static final Object[][] tables = new Object[TEST_CASES][]; 35 36 @BeforeExperiment setUp()37 public void setUp() { 38 int tableSize = ImmutableSet.chooseTableSize(size); 39 int mask = tableSize - 1; 40 for (int i = 0; i < TEST_CASES; i++) { 41 tables[i] = new Object[tableSize]; 42 for (int j = 0; j < size; j++) { 43 Object o = new Object(); 44 for (int k = o.hashCode(); ; k++) { 45 int index = k & mask; 46 if (tables[i][index] == null) { 47 tables[i][index] = o; 48 break; 49 } 50 } 51 } 52 } 53 } 54 55 enum Impl { 56 EXHAUSTIVE { maxRunBeforeFallback(int tableSize)57 int maxRunBeforeFallback(int tableSize) { 58 return 12 * IntMath.log2(tableSize, RoundingMode.UNNECESSARY); 59 } 60 61 @Override hashFloodingDetected(Object[] hashTable)62 boolean hashFloodingDetected(Object[] hashTable) { 63 int maxRunBeforeFallback = maxRunBeforeFallback(hashTable.length); 64 65 // Test for a run wrapping around the end of the table, then check for runs in the middle. 66 int endOfStartRun; 67 for (endOfStartRun = 0; endOfStartRun < hashTable.length; ) { 68 if (hashTable[endOfStartRun] == null) { 69 break; 70 } 71 endOfStartRun++; 72 if (endOfStartRun > maxRunBeforeFallback) { 73 return true; 74 } 75 } 76 int startOfEndRun; 77 for (startOfEndRun = hashTable.length - 1; startOfEndRun > endOfStartRun; startOfEndRun--) { 78 if (hashTable[startOfEndRun] == null) { 79 break; 80 } 81 if (endOfStartRun + (hashTable.length - 1 - startOfEndRun) > maxRunBeforeFallback) { 82 return true; 83 } 84 } 85 for (int i = endOfStartRun + 1; i < startOfEndRun; i++) { 86 for (int runLength = 0; i < startOfEndRun && hashTable[i] != null; i++) { 87 runLength++; 88 if (runLength > maxRunBeforeFallback) { 89 return true; 90 } 91 } 92 } 93 return false; 94 } 95 }, 96 SEPARATE_RANGES { maxRunBeforeFallback(int tableSize)97 int maxRunBeforeFallback(int tableSize) { 98 return 13 * IntMath.log2(tableSize, RoundingMode.UNNECESSARY); 99 } 100 101 @Override hashFloodingDetected(Object[] hashTable)102 boolean hashFloodingDetected(Object[] hashTable) { 103 int maxRunBeforeFallback = maxRunBeforeFallback(hashTable.length); 104 105 // Test for a run wrapping around the end of the table, then check for runs in the middle. 106 int endOfStartRun; 107 for (endOfStartRun = 0; endOfStartRun < hashTable.length; ) { 108 if (hashTable[endOfStartRun] == null) { 109 break; 110 } 111 endOfStartRun++; 112 if (endOfStartRun > maxRunBeforeFallback) { 113 return true; 114 } 115 } 116 int startOfEndRun; 117 for (startOfEndRun = hashTable.length - 1; startOfEndRun > endOfStartRun; startOfEndRun--) { 118 if (hashTable[startOfEndRun] == null) { 119 break; 120 } 121 if (endOfStartRun + (hashTable.length - 1 - startOfEndRun) > maxRunBeforeFallback) { 122 return true; 123 } 124 } 125 126 // If this part returns true, there is definitely a run of size maxRunBeforeFallback/2. 127 // If this part returns false, there are definitely no runs of size >= maxRunBeforeFallback. 128 int testBlockSize = maxRunBeforeFallback / 2; 129 for (int i = endOfStartRun + 1; i + testBlockSize <= startOfEndRun; i += testBlockSize) { 130 boolean runGood = false; 131 for (int j = 0; j < testBlockSize; j++) { 132 if (hashTable[i + j] == null) { 133 runGood = true; 134 break; 135 } 136 } 137 if (!runGood) { 138 return true; 139 } 140 } 141 return false; 142 } 143 }, 144 SKIPPING { maxRunBeforeFallback(int tableSize)145 int maxRunBeforeFallback(int tableSize) { 146 return 13 * IntMath.log2(tableSize, RoundingMode.UNNECESSARY); 147 } 148 149 @Override hashFloodingDetected(Object[] hashTable)150 boolean hashFloodingDetected(Object[] hashTable) { 151 int maxRunBeforeFallback = maxRunBeforeFallback(hashTable.length); 152 int mask = hashTable.length - 1; 153 154 // Invariant: all elements at indices in [knownRunStart, knownRunEnd) are nonnull. 155 // If knownRunStart == knownRunEnd, this is vacuously true. 156 // When knownRunEnd exceeds hashTable.length, it "wraps", detecting runs around the end 157 // of the table. 158 int knownRunStart = 0; 159 int knownRunEnd = 0; 160 161 outerLoop: 162 while (knownRunStart < hashTable.length) { 163 if (knownRunStart == knownRunEnd && hashTable[knownRunStart] == null) { 164 if (hashTable[(knownRunStart + maxRunBeforeFallback - 1) & mask] == null) { 165 // There are only maxRunBeforeFallback - 1 elements between here and there, 166 // so even if they were all nonnull, we wouldn't detect a hash flood. Therefore, 167 // we can skip them all. 168 knownRunStart += maxRunBeforeFallback; 169 } else { 170 knownRunStart++; // the only case in which maxRunEnd doesn't increase by mRBF 171 // happens about f * (1-f) for f = DESIRED_LOAD_FACTOR, so around 21% of the time 172 } 173 knownRunEnd = knownRunStart; 174 } else { 175 for (int j = knownRunStart + maxRunBeforeFallback - 1; j >= knownRunEnd; j--) { 176 if (hashTable[j & mask] == null) { 177 knownRunEnd = knownRunStart + maxRunBeforeFallback; 178 knownRunStart = j + 1; 179 continue outerLoop; 180 } 181 } 182 return true; 183 } 184 } 185 return false; 186 } 187 }; 188 hashFloodingDetected(Object[] array)189 abstract boolean hashFloodingDetected(Object[] array); 190 } 191 192 @Benchmark detect(int reps)193 public int detect(int reps) { 194 int count = 0; 195 for (int i = 0; i < reps; i++) { 196 if (impl.hashFloodingDetected(tables[i & 0xFF])) { 197 count++; 198 } 199 } 200 return count; 201 } 202 } 203