1 /* 2 * Copyright 2010-2013, Carrot Search s.c., Boznicza 11/56, Poznan, Poland 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 shark.internal.hppc 18 19 import java.util.Locale 20 21 /** 22 * Code from com.carrotsearch.hppc.LongLongScatterMap copy pasted, inlined and converted to Kotlin. 23 * 24 * See https://github.com/carrotsearch/hppc . 25 */ 26 internal class LongLongScatterMap constructor(expectedElements: Int = 4) { 27 interfacenull28 fun interface ForEachCallback { 29 fun onEntry(key: Long, value: Long) 30 } 31 32 /** 33 * The array holding keys. 34 */ 35 private var keys: LongArray = longArrayOf() 36 37 /** 38 * The array holding values. 39 */ 40 private var values: LongArray = longArrayOf() 41 42 /** 43 * The number of stored keys (assigned key slots), excluding the special 44 * "empty" key, if any (use [.size] instead). 45 * 46 * @see .size 47 */ 48 private var assigned: Int = 0 49 50 /** 51 * Mask for slot scans in [.keys]. 52 */ 53 private var mask: Int = 0 54 55 /** 56 * Expand (rehash) [.keys] when [.assigned] hits this value. 57 */ 58 private var resizeAt: Int = 0 59 60 /** 61 * Special treatment for the "empty slot" key marker. 62 */ 63 private var hasEmptyKey: Boolean = false 64 65 /** 66 * The load factor for [.keys]. 67 */ 68 private var loadFactor: Double = 0.75 69 70 val isEmpty: Boolean 71 get() = size == 0 72 73 init { 74 ensureCapacity(expectedElements) 75 } 76 setnull77 operator fun set( 78 key: Long, 79 value: Long 80 ): Long { 81 val mask = this.mask 82 if (key == 0L) { 83 hasEmptyKey = true 84 val previousValue = values[mask + 1] 85 values[mask + 1] = value 86 return previousValue 87 } else { 88 val keys = this.keys 89 var slot = hashKey(key) and mask 90 91 var existing = keys[slot] 92 while (existing != 0L) { 93 if (existing == key) { 94 val previousValue = values[slot] 95 values[slot] = value 96 return previousValue 97 } 98 slot = slot + 1 and mask 99 existing = keys[slot] 100 } 101 102 if (assigned == resizeAt) { 103 allocateThenInsertThenRehash(slot, key, value) 104 } else { 105 keys[slot] = key 106 values[slot] = value 107 } 108 109 assigned++ 110 return 0L 111 } 112 } 113 removenull114 fun remove(key: Long): Long { 115 val mask = this.mask 116 if (key == 0L) { 117 hasEmptyKey = false 118 val previousValue = values[mask + 1] 119 values[mask + 1] = 0L 120 return previousValue 121 } else { 122 val keys = this.keys 123 var slot = hashKey(key) and mask 124 125 var existing = keys[slot] 126 while (existing != 0L) { 127 if (existing == key) { 128 val previousValue = values[slot] 129 shiftConflictingKeys(slot) 130 return previousValue 131 } 132 slot = slot + 1 and mask 133 existing = keys[slot] 134 } 135 136 return 0L 137 } 138 } 139 140 /** 141 * Being given a key looks it up in the map and returns the slot where element sits, so it later 142 * can be retrieved with [getSlotValue]; return '-1' if element not found. 143 * Why so complicated and not just make [get] return null if value not found? The reason is performance: 144 * this approach prevents unnecessary boxing of the primitive long that would happen with nullable Long? 145 */ getSlotnull146 fun getSlot(key: Long): Int { 147 if (key == 0L) { 148 return if (hasEmptyKey) mask + 1 else -1 149 } else { 150 val keys = this.keys 151 val mask = this.mask 152 var slot = hashKey(key) and mask 153 154 var existing = keys[slot] 155 while (existing != 0L) { 156 if (existing == key) { 157 return slot 158 } 159 slot = slot + 1 and mask 160 existing = keys[slot] 161 } 162 163 return -1 164 } 165 } 166 167 /** 168 * Being given a slot of element retrieves it from the collection 169 */ getSlotValuenull170 fun getSlotValue(slot: Int): Long = values[slot] 171 172 /** 173 * Returns an element matching a provided [key]; throws [IllegalArgumentException] if element not found 174 */ 175 operator fun get(key: Long): Long { 176 val slot = getSlot(key) 177 require(slot != -1) { "Unknown key $key" } 178 179 return getSlotValue(slot) 180 } 181 forEachnull182 fun forEach(forEachCallback: ForEachCallback) { 183 val max = mask + 1 184 var slot = -1 185 186 exitWhile@ while (true) { 187 if (slot < max) { 188 var existing: Long 189 slot++ 190 while (slot < max) { 191 existing = keys[slot] 192 if (existing != 0L) { 193 forEachCallback.onEntry(existing, values[slot]) 194 continue@exitWhile 195 } 196 slot++ 197 } 198 } 199 200 if (slot == max && hasEmptyKey) { 201 slot++ 202 forEachCallback.onEntry(0L, values[max]) 203 continue@exitWhile 204 } 205 break@exitWhile 206 } 207 } 208 entrySequencenull209 fun entrySequence(): Sequence<LongLongPair> { 210 val max = mask + 1 211 var slot = -1 212 return generateSequence { 213 if (slot < max) { 214 var existing: Long 215 slot++ 216 while (slot < max) { 217 existing = keys[slot] 218 if (existing != 0L) { 219 return@generateSequence existing to values[slot] 220 } 221 slot++ 222 } 223 } 224 if (slot == max && hasEmptyKey) { 225 slot++ 226 return@generateSequence 0L to values[max] 227 } 228 return@generateSequence null 229 } 230 } 231 containsKeynull232 fun containsKey(key: Long): Boolean { 233 if (key == 0L) { 234 return hasEmptyKey 235 } else { 236 val keys = this.keys 237 val mask = this.mask 238 var slot = hashKey(key) and mask 239 240 var existing = keys[slot] 241 while (existing != 0L) { 242 if (existing == key) { 243 return true 244 } 245 slot = slot + 1 and mask 246 existing = keys[slot] 247 } 248 249 return false 250 } 251 } 252 releasenull253 fun release() { 254 assigned = 0 255 hasEmptyKey = false 256 257 allocateBuffers(HPPC.minBufferSize(4, loadFactor)) 258 } 259 260 val size: Int 261 get() { 262 return assigned + if (hasEmptyKey) 1 else 0 263 } 264 ensureCapacitynull265 fun ensureCapacity(expectedElements: Int) { 266 if (expectedElements > resizeAt) { 267 val prevKeys = this.keys 268 val prevValues = this.values 269 allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor)) 270 if (!isEmpty) { 271 rehash(prevKeys, prevValues) 272 } 273 } 274 } 275 hashKeynull276 private fun hashKey(key: Long): Int { 277 return HPPC.mixPhi(key) 278 } 279 280 /** 281 * Rehash from old buffers to new buffers. 282 */ rehashnull283 private fun rehash( 284 fromKeys: LongArray, 285 fromValues: LongArray 286 ) { 287 // Rehash all stored key/value pairs into the new buffers. 288 val keys = this.keys 289 val values = this.values 290 val mask = this.mask 291 var existing: Long 292 293 // Copy the zero element's slot, then rehash everything else. 294 var from = fromKeys.size - 1 295 keys[keys.size - 1] = fromKeys[from] 296 values[values.size - 1] = fromValues[from] 297 while (--from >= 0) { 298 existing = fromKeys[from] 299 if (existing != 0L) { 300 var slot = hashKey(existing) and mask 301 while (keys[slot] != 0L) { 302 slot = slot + 1 and mask 303 } 304 keys[slot] = existing 305 values[slot] = fromValues[from] 306 } 307 } 308 } 309 310 /** 311 * Allocate new internal buffers. This method attempts to allocate 312 * and assign internal buffers atomically (either allocations succeed or not). 313 */ allocateBuffersnull314 private fun allocateBuffers(arraySize: Int) { 315 316 // Ensure no change is done if we hit an OOM. 317 val prevKeys = this.keys 318 val prevValues = this.values 319 try { 320 val emptyElementSlot = 1 321 this.keys = LongArray(arraySize + emptyElementSlot) 322 this.values = LongArray(arraySize + emptyElementSlot) 323 } catch (e: OutOfMemoryError) { 324 this.keys = prevKeys 325 this.values = prevValues 326 throw RuntimeException( 327 String.format( 328 Locale.ROOT, 329 "Not enough memory to allocate buffers for rehashing: %d -> %d", 330 this.mask + 1, 331 arraySize 332 ), e 333 ) 334 } 335 336 this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor) 337 this.mask = arraySize - 1 338 } 339 340 /** 341 * This method is invoked when there is a new key/ value pair to be inserted into 342 * the buffers but there is not enough empty slots to do so. 343 * 344 * New buffers are allocated. If this succeeds, we know we can proceed 345 * with rehashing so we assign the pending element to the previous buffer 346 * (possibly violating the invariant of having at least one empty slot) 347 * and rehash all keys, substituting new buffers at the end. 348 */ allocateThenInsertThenRehashnull349 private fun allocateThenInsertThenRehash( 350 slot: Int, 351 pendingKey: Long, 352 pendingValue: Long 353 ) { 354 355 // Try to allocate new buffers first. If we OOM, we leave in a consistent state. 356 val prevKeys = this.keys 357 val prevValues = this.values 358 allocateBuffers(HPPC.nextBufferSize(mask + 1, size, loadFactor)) 359 360 // We have succeeded at allocating new data so insert the pending key/value at 361 // the free slot in the old arrays before rehashing. 362 prevKeys[slot] = pendingKey 363 prevValues[slot] = pendingValue 364 365 // Rehash old keys, including the pending key. 366 rehash(prevKeys, prevValues) 367 } 368 369 /** 370 * Shift all the slot-conflicting keys and values allocated to 371 * (and including) `slot`. 372 */ shiftConflictingKeysnull373 private fun shiftConflictingKeys(gapSlotArg: Int) { 374 var gapSlot = gapSlotArg 375 val keys = this.keys 376 val values = this.values 377 val mask = this.mask 378 379 // Perform shifts of conflicting keys to fill in the gap. 380 var distance = 0 381 while (true) { 382 val slot = gapSlot + ++distance and mask 383 val existing = keys[slot] 384 if (existing == 0L) { 385 break 386 } 387 388 val idealSlot = hashKey(existing) 389 val shift = slot - idealSlot and mask 390 if (shift >= distance) { 391 // Entry at this position was originally at or before the gap slot. 392 // Move the conflict-shifted entry to the gap's position and repeat the procedure 393 // for any entries to the right of the current position, treating it 394 // as the new gap. 395 keys[gapSlot] = existing 396 values[gapSlot] = values[slot] 397 gapSlot = slot 398 distance = 0 399 } 400 } 401 402 // Mark the last found gap slot without a conflict as empty. 403 keys[gapSlot] = 0L 404 values[gapSlot] = 0L 405 assigned-- 406 } 407 } 408