Skip to content

Commit 6183477

Browse files
Fixed a regression in page lookup times introduced by using optionals
1 parent a3f90cb commit 6183477

File tree

3 files changed

+116
-58
lines changed

3 files changed

+116
-58
lines changed

.swiftpm/xcode/xcshareddata/xcbaselines/CodableDatastoreTests.xcbaseline/8A10B1EA-398E-4822-A587-2E59AF1B7EBC.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
1212
<dict>
1313
<key>baselineAverage</key>
14-
<real>0.105339</real>
14+
<real>0.155911</real>
1515
<key>baselineIntegrationDisplayName</key>
1616
<string>Local Baseline</string>
1717
</dict>

Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift

Lines changed: 88 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,13 @@ extension DiskPersistence.Datastore.Index {
292292
/// - Parameters:
293293
/// - proposedEntry: The entry to use in comparison with other persisted entries.
294294
/// - pages: A collection of pages to check against.
295+
/// - pageBuilder: A closure that provides a cached Page object for the loaded page.
295296
/// - comparator: A comparator to determine order and equality between the proposed entry and a persisted one.
296297
/// - Returns: The index within the pages collection where the entry would reside.
297298
func pageIndex<T>(
298299
for proposedEntry: T,
299-
in pages: [LazyTask<DiskPersistence.Datastore.Page>?],
300+
in pages: [DatastoreIndexManifest.PageInfo],
301+
pageBuilder: (_ pageID: DatastorePageIdentifier) async -> DiskPersistence.Datastore.Page,
300302
comparator: (_ lhs: T, _ rhs: DatastorePageEntry) throws -> SortOrder
301303
) async throws -> Int? {
302304
var slice = pages[...]
@@ -316,41 +318,45 @@ extension DiskPersistence.Datastore.Index {
316318
var firstEntryOfPage: DatastorePageEntry?
317319

318320
/// Start checking the page at the middle index, continuing to scan until we build up enough of an entry to compare to.
319-
pageIterator: for page in pages[middle...] {
320-
guard let page else { continue }
321-
let blocks = try await page.value.blocks
322-
323-
/// Start scanning the page block-by-block, continuing to scan until we build up enough of an entry to compare to.
324-
for try await block in blocks {
325-
switch block {
326-
case .complete(let bytes):
327-
/// We have a complete entry, lets use it and stop scanning
328-
firstEntryOfPage = try DatastorePageEntry(bytes: bytes, isPartial: false)
329-
break pageIterator
330-
case .head(let bytes):
331-
/// We are starting an entry, but will need to go to the next page.
332-
bytesForFirstEntry = bytes
333-
case .slice(let bytes):
334-
/// In the first position, lets skip it.
335-
guard bytesForFirstEntry != nil else { continue }
336-
/// In the final position, lets save and continue.
337-
bytesForFirstEntry?.append(contentsOf: bytes)
338-
case .tail(let bytes):
339-
/// In the first position, lets skip it.
340-
guard bytesForFirstEntry != nil else { continue }
341-
/// In the final position, lets save and stop.
342-
bytesForFirstEntry?.append(contentsOf: bytes)
343-
firstEntryOfPage = try DatastorePageEntry(bytes: bytesForFirstEntry!, isPartial: false)
344-
break pageIterator
345-
}
321+
pageIterator: for pageInfo in pages[middle...] {
322+
switch pageInfo {
323+
case .removed: break
324+
case .added(let pageID), .existing(let pageID):
325+
let page = await pageBuilder(pageID)
326+
let blocks = try await page.blocks
346327

347-
/// If we have some bytes, attempt to decode them into an entry.
348-
if let bytesForFirstEntry {
349-
firstEntryOfPage = try? DatastorePageEntry(bytes: bytesForFirstEntry, isPartial: false)
328+
/// Start scanning the page block-by-block, continuing to scan until we build up enough of an entry to compare to.
329+
for try await block in blocks {
330+
switch block {
331+
case .complete(let bytes):
332+
/// We have a complete entry, lets use it and stop scanning
333+
firstEntryOfPage = try DatastorePageEntry(bytes: bytes, isPartial: false)
334+
break pageIterator
335+
case .head(let bytes):
336+
/// We are starting an entry, but will need to go to the next page.
337+
bytesForFirstEntry = bytes
338+
case .slice(let bytes):
339+
/// In the first position, lets skip it.
340+
guard bytesForFirstEntry != nil else { continue }
341+
/// In the final position, lets save and continue.
342+
bytesForFirstEntry?.append(contentsOf: bytes)
343+
case .tail(let bytes):
344+
/// In the first position, lets skip it.
345+
guard bytesForFirstEntry != nil else { continue }
346+
/// In the final position, lets save and stop.
347+
bytesForFirstEntry?.append(contentsOf: bytes)
348+
firstEntryOfPage = try DatastorePageEntry(bytes: bytesForFirstEntry!, isPartial: false)
349+
break pageIterator
350+
}
351+
352+
/// If we have some bytes, attempt to decode them into an entry.
353+
if let bytesForFirstEntry {
354+
firstEntryOfPage = try? DatastorePageEntry(bytes: bytesForFirstEntry, isPartial: false)
355+
}
356+
357+
/// If we have an entry, stop scanning as we can go ahead and operate on it.
358+
if firstEntryOfPage != nil { break pageIterator }
350359
}
351-
352-
/// If we have an entry, stop scanning as we can go ahead and operate on it.
353-
if firstEntryOfPage != nil { break pageIterator }
354360
}
355361

356362
/// If we had to advance a page and didn't yet start accumulating data, move our middle since it would be pointless to check that page again if the proposed entry was ordered after the persisted one we found.
@@ -390,30 +396,48 @@ extension DiskPersistence.Datastore.Index {
390396
cursor: DiskPersistence.InstanceCursor,
391397
entry: DatastorePageEntry
392398
) {
393-
try await entry(for: proposedEntry, in: try await orderedPages, comparator: comparator)
399+
try await entry(
400+
for: proposedEntry,
401+
in: try await manifest.orderedPages,
402+
pageBuilder: { await datastore.page(for: .init(index: self.id, page: $0)) },
403+
comparator: comparator
404+
)
394405
}
395406

396407
func entry<T>(
397408
for proposedEntry: T,
398-
in pages: [LazyTask<DiskPersistence.Datastore.Page>?],
409+
in pages: [DatastoreIndexManifest.PageInfo],
410+
pageBuilder: (_ pageID: DatastorePageIdentifier) async -> DiskPersistence.Datastore.Page,
399411
comparator: (_ lhs: T, _ rhs: DatastorePageEntry) throws -> SortOrder
400412
) async throws -> (
401413
cursor: DiskPersistence.InstanceCursor,
402414
entry: DatastorePageEntry
403415
) {
404416
/// Get the page the entry should reside on
405-
guard let startingPageIndex = try await pageIndex(for: proposedEntry, in: pages, comparator: comparator)
417+
guard
418+
let startingPageIndex = try await pageIndex(
419+
for: proposedEntry,
420+
in: pages,
421+
pageBuilder: pageBuilder,
422+
comparator: comparator
423+
)
406424
else { throw DatastoreInterfaceError.instanceNotFound }
407425

408426
var bytesForEntry: Bytes?
409427
var isEntryComplete = false
410428
var blocksForEntry: [DiskPersistence.CursorBlock] = []
411429
var pageIndex = startingPageIndex
412430

413-
pageIterator: for lazyPage in pages[startingPageIndex...] {
431+
pageIterator: for pageInfo in pages[startingPageIndex...] {
414432
defer { pageIndex += 1 }
415-
guard let lazyPage else { continue }
416-
let page = await lazyPage.value
433+
434+
let page: DiskPersistence.Datastore.Page
435+
switch pageInfo {
436+
case .removed: continue
437+
case .existing(let pageID), .added(let pageID):
438+
page = await pageBuilder(pageID)
439+
}
440+
417441
let blocks = try await page.blocks
418442
var blockIndex = 0
419443

@@ -484,16 +508,28 @@ extension DiskPersistence.Datastore.Index {
484508
for proposedEntry: T,
485509
comparator: (_ lhs: T, _ rhs: DatastorePageEntry) throws -> SortOrder
486510
) async throws -> DiskPersistence.InsertionCursor {
487-
try await insertionCursor(for: proposedEntry, in: try await orderedPages, comparator: comparator)
511+
try await insertionCursor(
512+
for: proposedEntry,
513+
in: try await manifest.orderedPages,
514+
pageBuilder: { await datastore.page(for: .init(index: self.id, page: $0)) },
515+
comparator: comparator
516+
)
488517
}
489518

490519
func insertionCursor<T>(
491520
for proposedEntry: T,
492-
in pages: [LazyTask<DiskPersistence.Datastore.Page>?],
521+
in pages: [DatastoreIndexManifest.PageInfo],
522+
pageBuilder: (_ pageID: DatastorePageIdentifier) async -> DiskPersistence.Datastore.Page,
493523
comparator: (_ lhs: T, _ rhs: DatastorePageEntry) throws -> SortOrder
494524
) async throws -> DiskPersistence.InsertionCursor {
495525
/// Get the page the entry should reside on
496-
guard let startingPageIndex = try await pageIndex(for: proposedEntry, in: pages, comparator: comparator)
526+
guard
527+
let startingPageIndex = try await pageIndex(
528+
for: proposedEntry,
529+
in: pages,
530+
pageBuilder: pageBuilder,
531+
comparator: comparator
532+
)
497533
else {
498534
return DiskPersistence.InsertionCursor(
499535
persistence: datastore.snapshot.persistence,
@@ -509,10 +545,16 @@ extension DiskPersistence.Datastore.Index {
509545
var currentBlock: DiskPersistence.CursorBlock? = nil
510546
var pageIndex = startingPageIndex
511547

512-
pageIterator: for lazyPage in pages[startingPageIndex...] {
548+
pageIterator: for pageInfo in pages[startingPageIndex...] {
513549
defer { pageIndex += 1 }
514-
guard let lazyPage else { continue }
515-
let page = await lazyPage.value
550+
551+
let page: DiskPersistence.Datastore.Page
552+
switch pageInfo {
553+
case .removed: continue
554+
case .existing(let pageID), .added(let pageID):
555+
page = await pageBuilder(pageID)
556+
}
557+
516558
let blocks = try await page.blocks
517559
var blockIndex = 0
518560

Tests/CodableDatastoreTests/DiskPersistenceDatastoreIndexTests.swift

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,26 @@ final class DiskPersistenceDatastoreIndexTests: XCTestCase {
4747
)
4848
)
4949

50-
let pages = pages.enumerated().map { (index, blocks) in
51-
DiskPersistence<ReadOnly>.Datastore.Page(
50+
var pageLookup: [DatastorePageIdentifier : DiskPersistence<ReadOnly>.Datastore.Page] = [:]
51+
var pageInfos: [DatastoreIndexManifest.PageInfo] = []
52+
53+
for (index, blocks) in pages.enumerated() {
54+
let pageID = DatastorePageIdentifier(rawValue: "Page \(index)")
55+
let page = DiskPersistence<ReadOnly>.Datastore.Page(
5256
datastore: datastore,
5357
id: .init(
5458
index: .primary(manifest: .init(rawValue: "Index")),
55-
page: .init(rawValue: "Page \(index)")
59+
page: pageID
5660
),
5761
blocks: blocks
5862
)
59-
}.map { page in LazyTask { page } }
63+
pageLookup[pageID] = page
64+
pageInfos.append(.existing(pageID))
65+
}
6066

61-
let result = try await index.pageIndex(for: proposedEntry, in: pages) { lhs, rhs in
67+
let result = try await index.pageIndex(for: proposedEntry, in: pageInfos) { pageID in
68+
pageLookup[pageID]!
69+
} comparator: { lhs, rhs in
6270
lhs.sortOrder(comparedTo: rhs.headers[0][0])
6371
}
6472

@@ -289,22 +297,30 @@ final class DiskPersistenceDatastoreIndexTests: XCTestCase {
289297
)
290298
)
291299

292-
let pages = pageBlocks.enumerated().map { (index, blocks) in
293-
DiskPersistence<ReadOnly>.Datastore.Page(
300+
var pageLookup: [DatastorePageIdentifier : DiskPersistence<ReadOnly>.Datastore.Page] = [:]
301+
var pageInfos: [DatastoreIndexManifest.PageInfo] = []
302+
303+
for (index, blocks) in pageBlocks.enumerated() {
304+
let pageID = DatastorePageIdentifier(rawValue: "Page \(index)")
305+
let page = DiskPersistence<ReadOnly>.Datastore.Page(
294306
datastore: datastore,
295307
id: .init(
296308
index: .primary(manifest: .init(rawValue: "Index")),
297-
page: .init(rawValue: "Page \(index)")
309+
page: pageID
298310
),
299311
blocks: blocks
300312
)
301-
}.map { page in LazyTask { page } }
313+
pageLookup[pageID] = page
314+
pageInfos.append(.existing(pageID))
315+
}
302316

303317
measure {
304318
let exp = expectation(description: "Finished")
305-
Task {
319+
Task { [pageInfos, pageLookup] in
306320
for _ in 0..<1000 {
307-
_ = try await index.pageIndex(for: UInt64.random(in: 0..<1000000), in: pages) { lhs, rhs in
321+
_ = try await index.pageIndex(for: UInt64.random(in: 0..<1000000), in: pageInfos) { pageID in
322+
pageLookup[pageID]!
323+
} comparator: { lhs, rhs in
308324
lhs.sortOrder(comparedTo: try UInt64(bigEndianBytes: rhs.headers[0]))
309325
}
310326
}

0 commit comments

Comments
 (0)