Skip to content

Commit 9e00b74

Browse files
committed
[WIP] Optimization: Improve performance for repetitive lookup
CacheLookupTable leaves the bucket vacant when the object is freed. Because of this, in the worst case, subscript have to perform full linear search for all buckets. Let's say we have a table like this: bucket 0: obj1(hash: 0) bucket 1: obj2(hash: 1) bucket 2: nil bucket 3: obj3(hash: 0) bucket 4: nil If obj1 is freed, it become: bucket 0: nil bucket 1: obj2(hash: 1) bucket 2: nil bucket 3: obj3(hash: 0) bucket 4: nil When looking-up obj3, the search starts from "bucket 0", thus it needs 3 iteration. This patch moves the obj3 to "bucket 0" the first nil hole found. So the next lookup doesn't need any iteration. bucket 0: obj3(hash: 0) bucket 1: obj2(hash: 1) bucket 2: nil bucket 3: nil bucket 4: nil
1 parent 1b5803c commit 9e00b74

File tree

1 file changed

+20
-4
lines changed

1 file changed

+20
-4
lines changed

Sources/SwiftSyntax/CacheLookupTable.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,15 +197,31 @@ class CacheLookupTable<T: Identifiable & AnyObject> {
197197
/// insert()-ed or it's already been freed.
198198
public subscript(id: T.Identifier) -> T? {
199199
// Since we don't fill the bucket when the value is freed (because we don't
200-
// know), we can't stop iteration a hole. So in the worst case (i.e. if the
201-
// object is not contained in this table), full linear search is required.
200+
// know), we can't stop iteration at a hole. So in the worst case (i.e. if
201+
// the object doesn't exist in the table), full linear search is needed.
202202
// However, since we assume the value exists and hasn't been freed yet,
203203
// we expect it's stored near the 'idealBucket' anyway.
204204
let idealBucket = _idealBucket(for: id)
205205
var bucket = idealBucket
206+
var firstHole: Int? = nil
206207
repeat {
207-
if buckets[bucket].value?.id == id {
208-
return buckets[bucket].value
208+
if let obj = buckets[bucket].value {
209+
if obj.id == id {
210+
if let firstHole = firstHole {
211+
// Move the object to the first hole found. This improves lookup
212+
// performance in the next lookup.
213+
let holeP = buckets + firstHole
214+
let bucketP = buckets + bucket
215+
216+
let tmp = holeP.move()
217+
holeP.moveInitialize(from: bucketP, count: 1)
218+
bucketP.initialize(to: tmp)
219+
}
220+
return obj
221+
}
222+
} else if firstHole == nil {
223+
// Remember the first hole found.
224+
firstHole = bucket
209225
}
210226
bucket = (bucket &+ 1) & _bucketMask
211227
} while bucket != idealBucket

0 commit comments

Comments
 (0)