OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:typed_data'; | 5 import 'dart:typed_data'; |
6 import 'dart:_internal' as internal; | 6 import 'dart:_internal' as internal; |
7 | 7 |
8 // Hash table with open addressing that separates the index from keys/values. | 8 // Hash table with open addressing that separates the index from keys/values. |
9 | 9 |
10 abstract class _HashFieldBase { | 10 abstract class _HashFieldBase { |
11 // Each occupied entry in _index is a fixed-size integer that encodes a pair: | 11 // Each occupied entry in _index is a fixed-size integer that encodes a pair: |
12 // [ hash pattern for key | index of entry in _data ] | 12 // [ hash pattern for key | index of entry in _data ] |
13 // The hash pattern is based on hashCode, but is guaranteed to be non-zero. | 13 // The hash pattern is based on hashCode, but is guaranteed to be non-zero. |
14 // The length of _index is always a power of two, and there is always at | 14 // The length of _index is always a power of two, and there is always at |
15 // least one unoccupied entry. | 15 // least one unoccupied entry. |
16 // NOTE: When maps are deserialized, their _index and _hashMask is regenerated | |
17 // lazily by _regenerateIndex. | |
18 // TODO(koda): Consider also using null _index for tiny, linear-search tables. | |
16 Uint32List _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE); | 19 Uint32List _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE); |
17 | 20 |
18 // Cached in-place mask for the hash pattern component. On 32-bit, the top | 21 // Cached in-place mask for the hash pattern component. |
19 // bits are wasted to avoid Mint allocation. | 22 int _hashMask = _HashBase._indexSizeToHashMask(_HashBase._INITIAL_INDEX_SIZE); |
20 // TODO(koda): Reclaim the bits by making the compiler treat hash patterns | |
21 // as unsigned words. | |
22 int _hashMask = internal.is64Bit ? | |
23 (1 << (32 - _HashBase._INITIAL_INDEX_BITS)) - 1 : | |
24 (1 << (30 - _HashBase._INITIAL_INDEX_BITS)) - 1; | |
25 | 23 |
26 // Fixed-length list of keys (set) or key/value at even/odd indices (map). | 24 // Fixed-length list of keys (set) or key/value at even/odd indices (map). |
27 List _data = new List(_HashBase._INITIAL_INDEX_SIZE); | 25 List _data = new List(_HashBase._INITIAL_INDEX_SIZE); |
28 | 26 |
29 // Length of _data that is used (i.e., keys + values for a map). | 27 // Length of _data that is used (i.e., keys + values for a map). |
30 int _usedData = 0; | 28 int _usedData = 0; |
31 | 29 |
32 // Number of deleted keys. | 30 // Number of deleted keys. |
33 int _deletedKeys = 0; | 31 int _deletedKeys = 0; |
34 } | 32 } |
(...skipping 25 matching lines...) Expand all Loading... | |
60 // The length of _index is twice the number of entries in _data, and both | 58 // The length of _index is twice the number of entries in _data, and both |
61 // are doubled when _data is full. Thus, _index will have a max load factor | 59 // are doubled when _data is full. Thus, _index will have a max load factor |
62 // of 1/2, which enables one more bit to be used for the hash. | 60 // of 1/2, which enables one more bit to be used for the hash. |
63 // TODO(koda): Consider growing _data by factor sqrt(2), twice as often. | 61 // TODO(koda): Consider growing _data by factor sqrt(2), twice as often. |
64 static const int _INITIAL_INDEX_BITS = 3; | 62 static const int _INITIAL_INDEX_BITS = 3; |
65 static const int _INITIAL_INDEX_SIZE = 1 << (_INITIAL_INDEX_BITS + 1); | 63 static const int _INITIAL_INDEX_SIZE = 1 << (_INITIAL_INDEX_BITS + 1); |
66 | 64 |
67 // Unused and deleted entries are marked by 0 and 1, respectively. | 65 // Unused and deleted entries are marked by 0 and 1, respectively. |
68 static const int _UNUSED_PAIR = 0; | 66 static const int _UNUSED_PAIR = 0; |
69 static const int _DELETED_PAIR = 1; | 67 static const int _DELETED_PAIR = 1; |
70 | 68 |
69 // On 32-bit, the top bits are wasted to avoid Mint allocation. | |
70 // TODO(koda): Reclaim the bits by making the compiler treat hash patterns | |
71 // as unsigned words. | |
72 static int _indexSizeToHashMask(int indexSize) { | |
73 int indexBits = indexSize.bitLength - 1; | |
74 return internal.is64Bit ? (1 << (32 - indexBits)) - 1 : | |
75 (1 << (30 - indexBits)) - 1; | |
76 } | |
77 | |
71 static int _hashPattern(int fullHash, int hashMask, int size) { | 78 static int _hashPattern(int fullHash, int hashMask, int size) { |
72 final int maskedHash = fullHash & hashMask; | 79 final int maskedHash = fullHash & hashMask; |
73 // TODO(koda): Consider keeping bit length and use left shift. | 80 // TODO(koda): Consider keeping bit length and use left shift. |
74 return (maskedHash == 0) ? (size >> 1) : maskedHash * (size >> 1); | 81 return (maskedHash == 0) ? (size >> 1) : maskedHash * (size >> 1); |
75 } | 82 } |
76 | 83 |
77 // Linear probing. | 84 // Linear probing. |
78 static int _firstProbe(int fullHash, int sizeMask) { | 85 static int _firstProbe(int fullHash, int sizeMask) { |
79 final int i = fullHash & sizeMask; | 86 final int i = fullHash & sizeMask; |
80 // Light, fast shuffle to mitigate bad hashCode (e.g., sequential). | 87 // Light, fast shuffle to mitigate bad hashCode (e.g., sequential). |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
125 // TODO(koda): Consider in-place compaction and more costly CME check. | 132 // TODO(koda): Consider in-place compaction and more costly CME check. |
126 _init(_index.length, _hashMask, _data, _usedData); | 133 _init(_index.length, _hashMask, _data, _usedData); |
127 } else { | 134 } else { |
128 // TODO(koda): Support 32->64 bit transition (and adjust _hashMask). | 135 // TODO(koda): Support 32->64 bit transition (and adjust _hashMask). |
129 _init(_index.length << 1, _hashMask >> 1, _data, _usedData); | 136 _init(_index.length << 1, _hashMask >> 1, _data, _usedData); |
130 } | 137 } |
131 } | 138 } |
132 | 139 |
133 void clear() { | 140 void clear() { |
134 if (!isEmpty) { | 141 if (!isEmpty) { |
135 _init(_index.length, _hashMask); | 142 // Use _data.length, since _index might be null. |
143 _init(_data.length, _hashMask); | |
136 } | 144 } |
137 } | 145 } |
138 | 146 |
139 // Allocate new _index and _data, and optionally copy existing contents. | 147 // Allocate new _index and _data, and optionally copy existing contents. |
140 void _init(int size, int hashMask, [List oldData, int oldUsed]) { | 148 void _init(int size, int hashMask, [List oldData, int oldUsed]) { |
141 assert(size & (size - 1) == 0); | 149 assert(size & (size - 1) == 0); |
142 assert(_HashBase._UNUSED_PAIR == 0); | 150 assert(_HashBase._UNUSED_PAIR == 0); |
143 _index = new Uint32List(size); | 151 _index = new Uint32List(size); |
144 _hashMask = hashMask; | 152 _hashMask = hashMask; |
145 _data = new List(size); | 153 _data = new List(size); |
146 _usedData = 0; | 154 _usedData = 0; |
147 _deletedKeys = 0; | 155 _deletedKeys = 0; |
148 if (oldData != null) { | 156 if (oldData != null) { |
149 for (int i = 0; i < oldUsed; i += 2) { | 157 for (int i = 0; i < oldUsed; i += 2) { |
150 var key = oldData[i]; | 158 var key = oldData[i]; |
151 if (!_HashBase._isDeleted(oldData, key)) { | 159 if (!_HashBase._isDeleted(oldData, key)) { |
152 // TODO(koda): While there are enough hash bits, avoid hashCode calls. | 160 // TODO(koda): While there are enough hash bits, avoid hashCode calls. |
153 this[key] = oldData[i + 1]; | 161 this[key] = oldData[i + 1]; |
154 } | 162 } |
155 } | 163 } |
156 } | 164 } |
157 } | 165 } |
166 | |
167 int _regenerateIndex() { | |
168 assert(_index == null); | |
169 _index = new Uint32List(_data.length); | |
170 assert(_hashMask == 0); | |
171 _hashMask = _HashBase._indexSizeToHashMask(_index.length); | |
172 final int tmpUsed = _usedData; | |
173 _usedData = 0; | |
174 for (int i = 0; i < tmpUsed; i += 2) { | |
175 // TODO(koda): Avoid redundant equality tests and stores into _data. | |
176 this[_data[i]] = _data[i + 1]; | |
177 } | |
178 return _index.length; | |
siva
2015/06/01 22:19:36
instead of checking every caller to this function
koda
2015/06/01 23:01:01
Since we don't do partial inlining (right?), that
siva
2015/06/02 00:13:35
I don't feel strongly about moving the null check
| |
179 } | |
158 | 180 |
159 void _insert(K key, V value, int hashPattern, int i) { | 181 void _insert(K key, V value, int hashPattern, int i) { |
160 if (_usedData == _data.length) { | 182 if (_usedData == _data.length) { |
161 _rehash(); | 183 _rehash(); |
162 this[key] = value; | 184 this[key] = value; |
163 } else { | 185 } else { |
164 assert(1 <= hashPattern && hashPattern < (1 << 32)); | 186 assert(1 <= hashPattern && hashPattern < (1 << 32)); |
165 final int index = _usedData >> 1; | 187 final int index = _usedData >> 1; |
166 assert((index & hashPattern) == 0); | 188 assert((index & hashPattern) == 0); |
167 _index[i] = hashPattern | index; | 189 _index[i] = hashPattern | index; |
(...skipping 24 matching lines...) Expand all Loading... | |
192 } | 214 } |
193 } | 215 } |
194 } | 216 } |
195 i = _HashBase._nextProbe(i, sizeMask); | 217 i = _HashBase._nextProbe(i, sizeMask); |
196 pair = _index[i]; | 218 pair = _index[i]; |
197 } | 219 } |
198 return firstDeleted >= 0 ? -firstDeleted : -i; | 220 return firstDeleted >= 0 ? -firstDeleted : -i; |
199 } | 221 } |
200 | 222 |
201 void operator[]=(K key, V value) { | 223 void operator[]=(K key, V value) { |
202 final int size = _index.length; | 224 final int size = (_index == null) ? _regenerateIndex() : _index.length; |
203 final int sizeMask = size - 1; | 225 final int sizeMask = size - 1; |
204 final int fullHash = _hashCode(key); | 226 final int fullHash = _hashCode(key); |
205 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 227 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
206 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); | 228 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); |
207 if (d > 0) { | 229 if (d > 0) { |
208 _data[d] = value; | 230 _data[d] = value; |
209 } else { | 231 } else { |
210 final int i = -d; | 232 final int i = -d; |
211 _insert(key, value, hashPattern, i); | 233 _insert(key, value, hashPattern, i); |
212 } | 234 } |
213 } | 235 } |
214 | 236 |
215 V putIfAbsent(K key, V ifAbsent()) { | 237 V putIfAbsent(K key, V ifAbsent()) { |
216 final int size = _index.length; | 238 final int size = (_index == null) ? _regenerateIndex() : _index.length; |
217 final int sizeMask = size - 1; | 239 final int sizeMask = size - 1; |
218 final int maxEntries = size >> 1; | 240 final int maxEntries = size >> 1; |
219 final int fullHash = _hashCode(key); | 241 final int fullHash = _hashCode(key); |
220 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 242 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
221 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); | 243 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); |
222 if (d > 0) { | 244 if (d > 0) { |
223 return _data[d]; | 245 return _data[d]; |
224 } | 246 } |
225 // 'ifAbsent' is allowed to modify the map. | 247 // 'ifAbsent' is allowed to modify the map. |
226 List oldData = _data; | 248 List oldData = _data; |
227 int oldCheckSum = _checkSum; | 249 int oldCheckSum = _checkSum; |
228 V value = ifAbsent(); | 250 V value = ifAbsent(); |
229 if (_isModifiedSince(oldData, oldCheckSum)) { | 251 if (_isModifiedSince(oldData, oldCheckSum)) { |
230 this[key] = value; | 252 this[key] = value; |
231 } else { | 253 } else { |
232 final int i = -d; | 254 final int i = -d; |
233 _insert(key, value, hashPattern, i); | 255 _insert(key, value, hashPattern, i); |
234 } | 256 } |
235 return value; | 257 return value; |
236 } | 258 } |
237 | 259 |
238 V remove(Object key) { | 260 V remove(Object key) { |
239 final int size = _index.length; | 261 final int size = (_index == null) ? _regenerateIndex() : _index.length; |
240 final int sizeMask = size - 1; | 262 final int sizeMask = size - 1; |
241 final int maxEntries = size >> 1; | 263 final int maxEntries = size >> 1; |
242 final int fullHash = _hashCode(key); | 264 final int fullHash = _hashCode(key); |
243 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 265 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
244 int i = _HashBase._firstProbe(fullHash, sizeMask); | 266 int i = _HashBase._firstProbe(fullHash, sizeMask); |
245 int pair = _index[i]; | 267 int pair = _index[i]; |
246 while (pair != _HashBase._UNUSED_PAIR) { | 268 while (pair != _HashBase._UNUSED_PAIR) { |
247 if (pair != _HashBase._DELETED_PAIR) { | 269 if (pair != _HashBase._DELETED_PAIR) { |
248 final int entry = hashPattern ^ pair; | 270 final int entry = hashPattern ^ pair; |
249 if (entry < maxEntries) { | 271 if (entry < maxEntries) { |
250 final int d = entry << 1; | 272 final int d = entry << 1; |
251 if (_equals(key, _data[d])) { | 273 if (_equals(key, _data[d])) { |
252 _index[i] = _HashBase._DELETED_PAIR; | 274 _index[i] = _HashBase._DELETED_PAIR; |
253 _HashBase._setDeletedAt(_data, d); | 275 _HashBase._setDeletedAt(_data, d); |
254 V value = _data[d + 1]; | 276 V value = _data[d + 1]; |
255 _HashBase._setDeletedAt(_data, d + 1); | 277 _HashBase._setDeletedAt(_data, d + 1); |
256 ++_deletedKeys; | 278 ++_deletedKeys; |
257 return value; | 279 return value; |
258 } | 280 } |
259 } | 281 } |
260 } | 282 } |
261 i = _HashBase._nextProbe(i, sizeMask); | 283 i = _HashBase._nextProbe(i, sizeMask); |
262 pair = _index[i]; | 284 pair = _index[i]; |
263 } | 285 } |
264 return null; | 286 return null; |
265 } | 287 } |
266 | 288 |
267 // If key is absent, return _data (which is never a value). | 289 // If key is absent, return _data (which is never a value). |
268 Object _getValueOrData(Object key) { | 290 Object _getValueOrData(Object key) { |
269 final int size = _index.length; | 291 final int size = (_index == null) ? _regenerateIndex() : _index.length; |
270 final int sizeMask = size - 1; | 292 final int sizeMask = size - 1; |
271 final int maxEntries = size >> 1; | 293 final int maxEntries = size >> 1; |
272 final int fullHash = _hashCode(key); | 294 final int fullHash = _hashCode(key); |
273 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 295 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
274 int i = _HashBase._firstProbe(fullHash, sizeMask); | 296 int i = _HashBase._firstProbe(fullHash, sizeMask); |
275 int pair = _index[i]; | 297 int pair = _index[i]; |
276 while (pair != _HashBase._UNUSED_PAIR) { | 298 while (pair != _HashBase._UNUSED_PAIR) { |
277 if (pair != _HashBase._DELETED_PAIR) { | 299 if (pair != _HashBase._DELETED_PAIR) { |
278 final int entry = hashPattern ^ pair; | 300 final int entry = hashPattern ^ pair; |
279 if (entry < maxEntries) { | 301 if (entry < maxEntries) { |
(...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
554 E lookup(Object o) => _validKey(o) ? super.lookup(o) : null; | 576 E lookup(Object o) => _validKey(o) ? super.lookup(o) : null; |
555 bool remove(Object o) => _validKey(o) ? super.remove(o) : false; | 577 bool remove(Object o) => _validKey(o) ? super.remove(o) : false; |
556 | 578 |
557 _CompactLinkedCustomHashSet(this._equality, this._hasher, validKey) | 579 _CompactLinkedCustomHashSet(this._equality, this._hasher, validKey) |
558 : _validKey = (validKey != null) ? validKey : new _TypeTest<E>().test; | 580 : _validKey = (validKey != null) ? validKey : new _TypeTest<E>().test; |
559 | 581 |
560 Set<E> toSet() => | 582 Set<E> toSet() => |
561 new _CompactLinkedCustomHashSet<E>(_equality, _hasher, _validKey) | 583 new _CompactLinkedCustomHashSet<E>(_equality, _hasher, _validKey) |
562 ..addAll(this); | 584 ..addAll(this); |
563 } | 585 } |
OLD | NEW |