1 /* 2 * Copyright (C) 2010 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 static com.google.common.collect.Lists.newArrayList; 20 21 import com.google.caliper.BeforeExperiment; 22 import com.google.caliper.Benchmark; 23 import com.google.caliper.Param; 24 import com.google.common.collect.CollectionBenchmarkSampleData.Element; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.concurrent.ConcurrentHashMap; 30 import java.util.concurrent.ConcurrentSkipListMap; 31 32 /** 33 * A microbenchmark that tests the performance of get() and iteration on various map 34 * implementations. Forked from {@link SetContainsBenchmark}. 35 * 36 * @author Nicholaus Shupe 37 */ 38 public class MapBenchmark { 39 @Param({"Hash", "LinkedHM", "MapMaker1", "Immutable"}) 40 private Impl impl; 41 42 public enum Impl { 43 Hash { 44 @Override create(Collection<Element> keys)45 Map<Element, Element> create(Collection<Element> keys) { 46 Map<Element, Element> map = Maps.newHashMap(); 47 for (Element element : keys) { 48 map.put(element, element); 49 } 50 return map; 51 } 52 }, 53 LinkedHM { 54 @Override create(Collection<Element> keys)55 Map<Element, Element> create(Collection<Element> keys) { 56 Map<Element, Element> map = Maps.newLinkedHashMap(); 57 for (Element element : keys) { 58 map.put(element, element); 59 } 60 return map; 61 } 62 }, 63 UnmodHM { 64 @Override create(Collection<Element> keys)65 Map<Element, Element> create(Collection<Element> keys) { 66 return Collections.unmodifiableMap(Hash.create(keys)); 67 } 68 }, 69 SyncHM { 70 @Override create(Collection<Element> keys)71 Map<Element, Element> create(Collection<Element> keys) { 72 return Collections.synchronizedMap(Hash.create(keys)); 73 } 74 }, 75 Tree { 76 @Override create(Collection<Element> keys)77 Map<Element, Element> create(Collection<Element> keys) { 78 Map<Element, Element> map = Maps.newTreeMap(); 79 for (Element element : keys) { 80 map.put(element, element); 81 } 82 return map; 83 } 84 }, 85 SkipList { 86 @Override create(Collection<Element> keys)87 Map<Element, Element> create(Collection<Element> keys) { 88 Map<Element, Element> map = new ConcurrentSkipListMap<>(); 89 for (Element element : keys) { 90 map.put(element, element); 91 } 92 return map; 93 } 94 }, 95 ConcurrentHM1 { 96 @Override create(Collection<Element> keys)97 Map<Element, Element> create(Collection<Element> keys) { 98 Map<Element, Element> map = new ConcurrentHashMap<>(keys.size(), 0.75f, 1); 99 for (Element element : keys) { 100 map.put(element, element); 101 } 102 return map; 103 } 104 }, 105 ConcurrentHM16 { 106 @Override create(Collection<Element> keys)107 Map<Element, Element> create(Collection<Element> keys) { 108 Map<Element, Element> map = new ConcurrentHashMap<>(keys.size(), 0.75f, 16); 109 for (Element element : keys) { 110 map.put(element, element); 111 } 112 return map; 113 } 114 }, 115 MapMaker1 { 116 @Override create(Collection<Element> keys)117 Map<Element, Element> create(Collection<Element> keys) { 118 Map<Element, Element> map = new MapMaker().concurrencyLevel(1).makeMap(); 119 for (Element element : keys) { 120 map.put(element, element); 121 } 122 return map; 123 } 124 }, 125 MapMaker16 { 126 @Override create(Collection<Element> keys)127 Map<Element, Element> create(Collection<Element> keys) { 128 Map<Element, Element> map = new MapMaker().concurrencyLevel(16).makeMap(); 129 for (Element element : keys) { 130 map.put(element, element); 131 } 132 return map; 133 } 134 }, 135 Immutable { 136 @Override create(Collection<Element> keys)137 Map<Element, Element> create(Collection<Element> keys) { 138 ImmutableMap.Builder<Element, Element> builder = ImmutableMap.builder(); 139 for (Element element : keys) { 140 builder.put(element, element); 141 } 142 return builder.buildOrThrow(); 143 } 144 }, 145 ImmutableSorted { 146 @Override create(Collection<Element> keys)147 Map<Element, Element> create(Collection<Element> keys) { 148 ImmutableSortedMap.Builder<Element, Element> builder = ImmutableSortedMap.naturalOrder(); 149 for (Element element : keys) { 150 builder.put(element, element); 151 } 152 return builder.build(); 153 } 154 }; 155 create(Collection<Element> contents)156 abstract Map<Element, Element> create(Collection<Element> contents); 157 } 158 159 @Param({"5", "50", "500", "5000", "50000"}) 160 private int size; 161 162 // TODO: look at exact (==) hits vs. equals() hits? 163 @Param("0.9") 164 private double hitRate; 165 166 @Param("true") 167 private boolean isUserTypeFast; 168 169 // "" means no fixed seed 170 @Param("") 171 private SpecialRandom random; 172 173 @Param("false") 174 private boolean sortedData; 175 176 // the following must be set during setUp 177 private Element[] queries; 178 private Map<Element, Element> mapToTest; 179 180 private Collection<Element> values; 181 182 @BeforeExperiment setUp()183 void setUp() { 184 CollectionBenchmarkSampleData sampleData = 185 new CollectionBenchmarkSampleData(isUserTypeFast, random, hitRate, size); 186 187 if (sortedData) { 188 List<Element> valueList = newArrayList(sampleData.getValuesInSet()); 189 Collections.sort(valueList); 190 values = valueList; 191 } else { 192 values = sampleData.getValuesInSet(); 193 } 194 this.mapToTest = impl.create(values); 195 this.queries = sampleData.getQueries(); 196 } 197 198 @Benchmark get(int reps)199 boolean get(int reps) { 200 // Paranoia: acting on hearsay that accessing fields might be slow 201 // Should write a benchmark to test that! 202 Map<Element, Element> map = mapToTest; 203 Element[] queries = this.queries; 204 205 // Allows us to use & instead of %, acting on hearsay that division 206 // operators (/%) are disproportionately expensive; should test this too! 207 int mask = queries.length - 1; 208 209 boolean dummy = false; 210 for (int i = 0; i < reps; i++) { 211 dummy ^= map.get(queries[i & mask]) != null; 212 } 213 return dummy; 214 } 215 216 @Benchmark createAndPopulate(int reps)217 int createAndPopulate(int reps) { 218 int dummy = 0; 219 for (int i = 0; i < reps; i++) { 220 dummy += impl.create(values).size(); 221 } 222 return dummy; 223 } 224 225 @Benchmark createPopulateAndRemove(int reps)226 boolean createPopulateAndRemove(int reps) { 227 boolean dummy = false; 228 for (int i = 1; i < reps; i++) { 229 Map<Element, Element> map = impl.create(values); 230 for (Element value : values) { 231 dummy |= map.remove(value) == null; 232 } 233 } 234 return dummy; 235 } 236 237 @Benchmark iterateWithEntrySet(int reps)238 boolean iterateWithEntrySet(int reps) { 239 Map<Element, Element> map = mapToTest; 240 241 boolean dummy = false; 242 for (int i = 0; i < reps; i++) { 243 for (Map.Entry<Element, Element> entry : map.entrySet()) { 244 dummy ^= entry.getKey() != entry.getValue(); 245 } 246 } 247 return dummy; 248 } 249 250 @Benchmark iterateWithKeySetAndGet(int reps)251 boolean iterateWithKeySetAndGet(int reps) { 252 Map<Element, Element> map = mapToTest; 253 254 boolean dummy = false; 255 for (int i = 0; i < reps; i++) { 256 for (Element key : map.keySet()) { 257 Element value = map.get(key); 258 dummy ^= key != value; 259 } 260 } 261 return dummy; 262 } 263 264 @Benchmark iterateValuesAndGet(int reps)265 boolean iterateValuesAndGet(int reps) { 266 Map<Element, Element> map = mapToTest; 267 268 boolean dummy = false; 269 for (int i = 0; i < reps; i++) { 270 for (Element key : map.values()) { 271 // This normally wouldn't make sense, but because our keys are our values it kind of does 272 Element value = map.get(key); 273 dummy ^= key != value; 274 } 275 } 276 return dummy; 277 } 278 } 279