Skip to content

Commit 7db8475

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 4c89b4f commit 7db8475

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
@@ -195,15 +195,31 @@ class CacheLookupTable<T: Identifiable & AnyObject> {
195195
/// insert()-ed or it's already been freed.
196196
public subscript(id: T.Identifier) -> T? {
197197
// Since we don't fill the bucket when the value is freed (because we don't
198-
// know), we can't stop iteration a hole. So in the worst case (i.e. if the
199-
// object is not contained in this table), full linear search is required.
198+
// know), we can't stop iteration at a hole. So in the worst case (i.e. if
199+
// the object doesn't exist in the table), full linear search is needed.
200200
// However, since we assume the value exists and hasn't been freed yet,
201201
// we expect it's stored near the 'idealBucket' anyway.
202202
let idealBucket = _idealBucket(for: id)
203203
var bucket = idealBucket
204+
var firstHole: Int? = nil
204205
repeat {
205-
if buckets[bucket].value?.id == id {
206-
return buckets[bucket].value
206+
if let obj = buckets[bucket].value {
207+
if obj.id == id {
208+
if let firstHole = firstHole {
209+
// Move the object to the first hole found. This improves lookup
210+
// performance in the next lookup.
211+
let holeP = buckets + firstHole
212+
let bucketP = buckets + bucket
213+
214+
let tmp = holeP.move()
215+
holeP.moveInitialize(from: bucketP, count: 1)
216+
bucketP.initialize(to: tmp)
217+
}
218+
return obj
219+
}
220+
} else if firstHole == nil {
221+
// Remember the first hole found.
222+
firstHole = bucket
207223
}
208224
bucket = (bucket &+ 1) & _bucketMask
209225
} while bucket != idealBucket

0 commit comments

Comments
 (0)