• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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