001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.hadoop.util; 019 020 import java.io.PrintStream; 021 import java.util.ConcurrentModificationException; 022 import java.util.Iterator; 023 024 import org.apache.hadoop.HadoopIllegalArgumentException; 025 import org.apache.hadoop.classification.InterfaceAudience; 026 import org.apache.hadoop.util.StringUtils; 027 028 import com.google.common.annotations.VisibleForTesting; 029 030 /** 031 * A low memory footprint {@link GSet} implementation, 032 * which uses an array for storing the elements 033 * and linked lists for collision resolution. 034 * 035 * No rehash will be performed. 036 * Therefore, the internal array will never be resized. 037 * 038 * This class does not support null element. 039 * 040 * This class is not thread safe. 041 * 042 * @param <K> Key type for looking up the elements 043 * @param <E> Element type, which must be 044 * (1) a subclass of K, and 045 * (2) implementing {@link LinkedElement} interface. 046 */ 047 @InterfaceAudience.Private 048 public class LightWeightGSet<K, E extends K> implements GSet<K, E> { 049 /** 050 * Elements of {@link LightWeightGSet}. 051 */ 052 public static interface LinkedElement { 053 /** Set the next element. */ 054 public void setNext(LinkedElement next); 055 056 /** Get the next element. */ 057 public LinkedElement getNext(); 058 } 059 060 static final int MAX_ARRAY_LENGTH = 1 << 30; //prevent int overflow problem 061 static final int MIN_ARRAY_LENGTH = 1; 062 063 /** 064 * An internal array of entries, which are the rows of the hash table. 065 * The size must be a power of two. 066 */ 067 private final LinkedElement[] entries; 068 /** A mask for computing the array index from the hash value of an element. */ 069 private final int hash_mask; 070 /** The size of the set (not the entry array). */ 071 private int size = 0; 072 /** Modification version for fail-fast. 073 * @see ConcurrentModificationException 074 */ 075 private int modification = 0; 076 077 /** 078 * @param recommended_length Recommended size of the internal array. 079 */ 080 public LightWeightGSet(final int recommended_length) { 081 final int actual = actualArrayLength(recommended_length); 082 if (LOG.isDebugEnabled()) { 083 LOG.debug("recommended=" + recommended_length + ", actual=" + actual); 084 } 085 entries = new LinkedElement[actual]; 086 hash_mask = entries.length - 1; 087 } 088 089 //compute actual length 090 private static int actualArrayLength(int recommended) { 091 if (recommended > MAX_ARRAY_LENGTH) { 092 return MAX_ARRAY_LENGTH; 093 } else if (recommended < MIN_ARRAY_LENGTH) { 094 return MIN_ARRAY_LENGTH; 095 } else { 096 final int a = Integer.highestOneBit(recommended); 097 return a == recommended? a: a << 1; 098 } 099 } 100 101 @Override 102 public int size() { 103 return size; 104 } 105 106 private int getIndex(final K key) { 107 return key.hashCode() & hash_mask; 108 } 109 110 private E convert(final LinkedElement e){ 111 @SuppressWarnings("unchecked") 112 final E r = (E)e; 113 return r; 114 } 115 116 @Override 117 public E get(final K key) { 118 //validate key 119 if (key == null) { 120 throw new NullPointerException("key == null"); 121 } 122 123 //find element 124 final int index = getIndex(key); 125 for(LinkedElement e = entries[index]; e != null; e = e.getNext()) { 126 if (e.equals(key)) { 127 return convert(e); 128 } 129 } 130 //element not found 131 return null; 132 } 133 134 @Override 135 public boolean contains(final K key) { 136 return get(key) != null; 137 } 138 139 @Override 140 public E put(final E element) { 141 //validate element 142 if (element == null) { 143 throw new NullPointerException("Null element is not supported."); 144 } 145 if (!(element instanceof LinkedElement)) { 146 throw new HadoopIllegalArgumentException( 147 "!(element instanceof LinkedElement), element.getClass()=" 148 + element.getClass()); 149 } 150 final LinkedElement e = (LinkedElement)element; 151 152 //find index 153 final int index = getIndex(element); 154 155 //remove if it already exists 156 final E existing = remove(index, element); 157 158 //insert the element to the head of the linked list 159 modification++; 160 size++; 161 e.setNext(entries[index]); 162 entries[index] = e; 163 164 return existing; 165 } 166 167 /** 168 * Remove the element corresponding to the key, 169 * given key.hashCode() == index. 170 * 171 * @return If such element exists, return it. 172 * Otherwise, return null. 173 */ 174 private E remove(final int index, final K key) { 175 if (entries[index] == null) { 176 return null; 177 } else if (entries[index].equals(key)) { 178 //remove the head of the linked list 179 modification++; 180 size--; 181 final LinkedElement e = entries[index]; 182 entries[index] = e.getNext(); 183 e.setNext(null); 184 return convert(e); 185 } else { 186 //head != null and key is not equal to head 187 //search the element 188 LinkedElement prev = entries[index]; 189 for(LinkedElement curr = prev.getNext(); curr != null; ) { 190 if (curr.equals(key)) { 191 //found the element, remove it 192 modification++; 193 size--; 194 prev.setNext(curr.getNext()); 195 curr.setNext(null); 196 return convert(curr); 197 } else { 198 prev = curr; 199 curr = curr.getNext(); 200 } 201 } 202 //element not found 203 return null; 204 } 205 } 206 207 @Override 208 public E remove(final K key) { 209 //validate key 210 if (key == null) { 211 throw new NullPointerException("key == null"); 212 } 213 return remove(getIndex(key), key); 214 } 215 216 @Override 217 public Iterator<E> iterator() { 218 return new SetIterator(); 219 } 220 221 @Override 222 public String toString() { 223 final StringBuilder b = new StringBuilder(getClass().getSimpleName()); 224 b.append("(size=").append(size) 225 .append(String.format(", %08x", hash_mask)) 226 .append(", modification=").append(modification) 227 .append(", entries.length=").append(entries.length) 228 .append(")"); 229 return b.toString(); 230 } 231 232 /** Print detailed information of this object. */ 233 public void printDetails(final PrintStream out) { 234 out.print(this + ", entries = ["); 235 for(int i = 0; i < entries.length; i++) { 236 if (entries[i] != null) { 237 LinkedElement e = entries[i]; 238 out.print("\n " + i + ": " + e); 239 for(e = e.getNext(); e != null; e = e.getNext()) { 240 out.print(" -> " + e); 241 } 242 } 243 } 244 out.println("\n]"); 245 } 246 247 private class SetIterator implements Iterator<E> { 248 /** The starting modification for fail-fast. */ 249 private int iterModification = modification; 250 /** The current index of the entry array. */ 251 private int index = -1; 252 private LinkedElement cur = null; 253 private LinkedElement next = nextNonemptyEntry(); 254 255 /** Find the next nonempty entry starting at (index + 1). */ 256 private LinkedElement nextNonemptyEntry() { 257 for(index++; index < entries.length && entries[index] == null; index++); 258 return index < entries.length? entries[index]: null; 259 } 260 261 private void ensureNext() { 262 if (modification != iterModification) { 263 throw new ConcurrentModificationException("modification=" + modification 264 + " != iterModification = " + iterModification); 265 } 266 if (next != null) { 267 return; 268 } 269 if (cur == null) { 270 return; 271 } 272 next = cur.getNext(); 273 if (next == null) { 274 next = nextNonemptyEntry(); 275 } 276 } 277 278 @Override 279 public boolean hasNext() { 280 ensureNext(); 281 return next != null; 282 } 283 284 @Override 285 public E next() { 286 ensureNext(); 287 if (next == null) { 288 throw new IllegalStateException("There are no more elements"); 289 } 290 cur = next; 291 next = null; 292 return convert(cur); 293 } 294 295 @SuppressWarnings("unchecked") 296 @Override 297 public void remove() { 298 ensureNext(); 299 if (cur == null) { 300 throw new IllegalStateException("There is no current element " + 301 "to remove"); 302 } 303 LightWeightGSet.this.remove((K)cur); 304 iterModification++; 305 cur = null; 306 } 307 } 308 309 /** 310 * Let t = percentage of max memory. 311 * Let e = round(log_2 t). 312 * Then, we choose capacity = 2^e/(size of reference), 313 * unless it is outside the close interval [1, 2^30]. 314 */ 315 public static int computeCapacity(double percentage, String mapName) { 316 return computeCapacity(Runtime.getRuntime().maxMemory(), percentage, 317 mapName); 318 } 319 320 @VisibleForTesting 321 static int computeCapacity(long maxMemory, double percentage, 322 String mapName) { 323 if (percentage > 100.0 || percentage < 0.0) { 324 throw new HadoopIllegalArgumentException("Percentage " + percentage 325 + " must be greater than or equal to 0 " 326 + " and less than or equal to 100"); 327 } 328 if (maxMemory < 0) { 329 throw new HadoopIllegalArgumentException("Memory " + maxMemory 330 + " must be greater than or equal to 0"); 331 } 332 if (percentage == 0.0 || maxMemory == 0) { 333 return 0; 334 } 335 //VM detection 336 //See http://java.sun.com/docs/hotspot/HotSpotFAQ.html#64bit_detection 337 final String vmBit = System.getProperty("sun.arch.data.model"); 338 339 //Percentage of max memory 340 final double percentDivisor = 100.0/percentage; 341 final double percentMemory = maxMemory/percentDivisor; 342 343 //compute capacity 344 final int e1 = (int)(Math.log(percentMemory)/Math.log(2.0) + 0.5); 345 final int e2 = e1 - ("32".equals(vmBit)? 2: 3); 346 final int exponent = e2 < 0? 0: e2 > 30? 30: e2; 347 final int c = 1 << exponent; 348 349 LOG.info("Computing capacity for map " + mapName); 350 LOG.info("VM type = " + vmBit + "-bit"); 351 LOG.info(percentage + "% max memory " 352 + StringUtils.TraditionalBinaryPrefix.long2String(maxMemory, "B", 1) 353 + " = " 354 + StringUtils.TraditionalBinaryPrefix.long2String((long) percentMemory, 355 "B", 1)); 356 LOG.info("capacity = 2^" + exponent + " = " + c + " entries"); 357 return c; 358 } 359 360 public void clear() { 361 for (int i = 0; i < entries.length; i++) { 362 entries[i] = null; 363 } 364 size = 0; 365 } 366 }