Skip to content

Commit b9da9f6

Browse files
lib: improve performance of Mutable_Hash_Map
when many succeeding hashes have conflicts
1 parent 9f810a8 commit b9da9f6

1 file changed

Lines changed: 45 additions & 47 deletions

File tree

modules/base/src/container/Mutable_Hash_Map.fz

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ pre
6868

6969
is
7070

71+
# element at this index was deleted, this index should be treated as occupied when looking up elements
72+
# to not cause unexpected gaps that could lead to lost elements
73+
# for insertions it is treated the same as an empty index
74+
del is
75+
7176
# number of entries currently in this map
7277
#
7378
load := mut 0
@@ -86,7 +91,7 @@ is
8691

8792
# the contents
8893
#
89-
contents := mut (LM.env.new_array (option (tuple HK V)) allocated_size.get.as_i64 nil)
94+
contents := mut (LM.env.new_array (choice nil del (tuple HK V)) allocated_size.get.as_i64 nil)
9095

9196

9297
# calculate the index of k within contents array in case of no conflict
@@ -101,7 +106,7 @@ is
101106
# return the next alternative position to check
102107
#
103108
collision (at i64) =>
104-
(at + 1) % allocated_size.get.as_i64 # NYI: dumb collision function, check literature and improve!
109+
(at + 904644503766293287) % allocated_size.get.as_i64
105110

106111

107112
# increase internal allocation if upper_load_factor is reached
@@ -127,26 +132,40 @@ is
127132
=>
128133
allocated_size <- new_alloc_size
129134
old_contents := contents.get.as_array
130-
contents <- LM.env.new_array (option (tuple HK V)) allocated_size.get.as_i64 nil
131-
for tup in (old_contents.filter o->o??) do
132-
ok, ov := tup.get
133-
store (idx ok) ok ov false
135+
contents <- LM.env.new_array (choice nil del (tuple HK V)) allocated_size.get.as_i64 nil
136+
for elem in old_contents do
137+
match elem
138+
t tuple HK V =>
139+
ok, ov := t
140+
store (idx ok) ok ov false false
141+
* => # noting to add
134142

135143

136144
# store key value pair at given index
137145
# updates value for existing key, does conflict resolution
138146
#
139-
store (at i64, k HK, v V, update_size bool) =>
147+
store (at i64, k HK, v V, update_size bool, is_update bool) =>
148+
140149
match contents.get[at]
141150
nil =>
142-
contents.get[at] := (k, v) # insert new key value pair
143-
if update_size then load <- load.get + 1
151+
if is_update
152+
store (idx k) k v update_size false
153+
else
154+
contents.get[at] := (k, v) # insert new key value pair
155+
if update_size then load <- load.get + 1
156+
144157
t tuple =>
145158
ek, _ := t
146159
if ek = k # no conflict, but remapping of k
147160
contents.get[at] := (k, v)
148161
else # conflict
149-
store (collision at) k v update_size
162+
store (collision at) k v update_size is_update
163+
del =>
164+
if is_update
165+
store (collision at) k v update_size is_update
166+
else
167+
contents.get[at] := (k, v) # insert new key value pair
168+
if update_size then load <- load.get + 1
150169

151170

152171
# number of entries in this map
@@ -160,7 +179,7 @@ is
160179
#
161180
public redef put(k HK, v V) unit =>
162181

163-
store (idx k) k v true
182+
store (idx k) k v true true
164183

165184
increase_if_necessary
166185

@@ -169,66 +188,45 @@ is
169188
#
170189
public redef get(k HK) option V =>
171190

172-
retrieve (at i64) option V =>
191+
retrieve (at i64, stop_at_start bool) option V =>
173192
match contents.get[at]
174193
nil => nil
175194
t tuple =>
176195
ek, v := t
177196
if ek = k
178197
v
179198
else
180-
retrieve (collision at)
199+
retrieve (collision at) true
200+
del =>
201+
(at = start_idx && stop_at_start) ? nil : retrieve (collision at) true
181202

182-
retrieve (idx k)
203+
start_idx := idx k
204+
retrieve start_idx false
183205

184206

185207
# remove key from map, returns the removed value
186208
#
187209
public redef remove(k HK) option V =>
188210

189211

190-
del(del_idx i64) option V =>
212+
delete(del_idx i64, stop_at_start bool) option V =>
191213
match contents.get[del_idx]
192214
nil => nil
193215
t tuple =>
194216
ek, v := t
195217
if ek = k
196218
contents.get[del_idx] := nil
197219
load <- load.get - 1
198-
fill_hole del_idx (collision del_idx)
220+
contents.get[del_idx] := del
199221
v
200222
else
201-
del (collision del_idx)
223+
delete (collision del_idx) true
224+
del =>
225+
(stop_at_start && del_idx = del_start_idx) ? nil : delete (collision del_idx) true
202226

227+
del_start_idx := idx k
203228

204-
# move elements if they should be in the position of the removed element
205-
# recursively filles new holes
206-
#
207-
fill_hole(cur_idx, next_idx i64) =>
208-
match contents.get[next_idx]
209-
nil => # done
210-
t tuple =>
211-
ek, _ := t
212-
213-
if (
214-
# not having passed array end: move elements saved at a larger index
215-
(next_idx >= cur_idx && idx ek <= cur_idx) ||
216-
217-
# element index is larger then array index (collision moved it beyond end):
218-
# - always move to left
219-
# - move to right (beyond beginning into the end) if it can normally be saved at that index
220-
(idx ek > next_idx && (cur_idx < next_idx || idx ek <= cur_idx)) ||
221-
222-
# having passed array end: move elements saved at a larger index only if they get moved left (i.e. not beyond the beginning into the end)
223-
(cur_idx < cur_idx && next_idx < cur_idx && idx ek <= cur_idx))
224-
then
225-
contents.get[cur_idx] := t
226-
contents.get[next_idx] := nil
227-
fill_hole next_idx (collision next_idx)
228-
else
229-
fill_hole cur_idx (collision next_idx)
230-
231-
return_val := del (idx k)
229+
return_val := delete del_start_idx false
232230

233231
decrease_if_necessary
234232

@@ -239,8 +237,8 @@ is
239237
#
240238
public redef items Sequence (tuple HK V) =>
241239
contents.get.as_array
242-
.filter o->o??
243-
.map (o -> o.or_else (panic "filter failed"))
240+
.filter (o -> o ? tuple => true | * => false)
241+
.map (x -> x ? t tuple => t | * => panic "filter failed" )
244242

245243

246244
# create an immutable map from this

0 commit comments

Comments
 (0)