OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 part of dart._internal; | |
6 | |
7 class CacheAssociation<K,V> { | |
Ivan Posva
2014/08/06 23:05:14
Why <K,V>?
| |
8 K key; | |
9 V value; | |
10 bool usedSinceGrowth = false; | |
11 } | |
12 | |
13 /** | |
14 * A map that will grow as associations are added but will prefer to evict | |
15 * associations that have not been used since the last growth when needing to | |
16 * grow again. Implemented as an open addressing hash table. | |
17 */ | |
18 class Cache<K,V> { | |
19 final CacheAssociation<K,V> _head; | |
20 List _table; | |
21 int _shift; | |
22 int _mask; | |
23 int _capacity; // Max number of associations before we start evicting. | |
24 int _size = 0; // Current number of associations. | |
25 | |
26 /** | |
27 * Create a Cache whose capacity is 75% of 2^shift. | |
28 */ | |
29 Cache.withInitialShift(int shift) | |
30 { | |
31 // The scheme used here for handling collisions relies on there always | |
32 // being at least one empty slot. | |
33 if (shift < 1) throw new Exception("Cache requires a shift >= 1"); | |
34 _initWithShift(shift); | |
35 } | |
36 | |
37 void _initWithShift(int shift) { | |
38 this._shift = shift; | |
39 this._mask = (1 << shift) - 1; | |
40 this._capacity = (1 << shift) * 3 ~/ 4; | |
41 this._table = new List(1 << shift); | |
42 assert(_table.length > _capacity); | |
43 } | |
44 | |
45 int _scanFor(K key) { | |
46 var start = key.hashCode & _mask; | |
47 var index = start; | |
48 do { | |
49 var assoc = _table[index]; | |
50 if (null == assoc || assoc.key == key) { | |
51 return index; | |
52 } | |
53 index = (index + 1) & _mask; | |
54 } while (index != start); | |
55 // Should never happen because we start evicting associations before the | |
56 // table is full. | |
57 throw new Exception("Internal error: LRUMap table full"); | |
Ivan Posva
2014/08/06 23:05:14
LRUMap?
| |
58 } | |
59 | |
60 int _scanForEmpty(K key) { | |
61 var start = key.hashCode & _mask; | |
62 var index = start; | |
63 do { | |
64 if (null == _table[index]) { | |
65 return index; | |
66 } | |
67 index = (index + 1) & _mask; | |
68 } while (index != start); | |
69 // Should never happen because we start evicting associations before the | |
70 // table is full. | |
71 throw new Exception("Internal error: LRUMap table full"); | |
Ivan Posva
2014/08/06 23:05:14
ditto
| |
72 } | |
73 | |
74 void _fixCollisionsAfter(start) { | |
75 var assoc; | |
76 var index = (start + 1) & _mask; | |
77 while (null != (assoc = _table[index])) { | |
Ivan Posva
2014/08/06 23:05:14
Assignment in condition?
rmacnak
2014/08/07 20:05:25
Yes, Dart doesn't have sequence expressions to wri
| |
78 var newIndex = _scanFor(assoc.key); | |
79 if (newIndex != index) { | |
80 assert(_table[newIndex] == null); | |
81 _table[newIndex] = assoc; | |
82 _table[index] = null; | |
83 } | |
84 index = (index + 1) & _mask; | |
85 } | |
86 } | |
87 | |
88 void _grow() { | |
89 var oldTable = _table; | |
90 | |
91 _initWithShift(_shift + 1); | |
92 | |
93 for (int oldIndex = 0; oldIndex < oldTable.length; oldIndex++) { | |
94 var assoc = oldTable[oldIndex]; | |
95 if (assoc != null) { | |
96 var newIndex = _scanForEmpty(assoc.key); | |
97 assoc.usedSinceGrowth = false; | |
98 _table[newIndex] = assoc; | |
99 } | |
100 } | |
101 } | |
102 | |
103 void _tryToShrinkOtherwiseGrow() { | |
104 bool needToGrow = true; | |
105 for (int i = 0; i < _table.length; i++) { | |
106 var assoc = _table[i]; | |
107 if (null != assoc && !assoc.usedSinceGrowth) { | |
108 _table[i] = null; | |
109 _size--; | |
110 _fixCollisionsAfter(i); | |
111 needToGrow = false; | |
112 } | |
113 } | |
114 if (needToGrow) _grow(); | |
115 } | |
116 | |
117 operator []=(K key, V value) { | |
118 int index = _scanFor(key); | |
119 var assoc = _table[index]; | |
120 if (null != assoc) { | |
121 // Existing key, replace value. | |
122 assert(assoc.key == key); | |
123 assoc.value = value; | |
124 assoc.usedSinceGrowth = true; | |
125 } else { | |
126 // New key. | |
127 var newAssoc = new CacheAssociation(); | |
128 if (_size == _capacity) { | |
129 // No free slots. | |
130 _tryToShrinkOtherwiseGrow(); | |
131 index = _scanFor(key); | |
Ivan Posva
2014/08/06 23:05:14
assert(table[index] == null);
| |
132 } | |
133 newAssoc.key = key; | |
134 newAssoc.value = value; | |
135 newAssoc.usedSinceGrowth = true; | |
136 _table[index] = newAssoc; | |
137 _size++; | |
138 } | |
Ivan Posva
2014/08/06 23:05:14
What is missing here is the deletion of an entry i
rmacnak
2014/08/07 20:05:25
This doesn't occur in practice, but added the logi
| |
139 } | |
140 | |
141 V operator [](K key) { | |
142 var index = _scanFor(key); | |
143 var assoc = _table[index]; | |
144 if (null == assoc) return null; | |
145 assoc.usedSinceGrowth = true; | |
146 return assoc.value; | |
147 } | |
148 } | |
OLD | NEW |