Skip to content

Commit 14f09e4

Browse files
[Chunked] Improving index(offset:limit:) to avoid distance call
1 parent 622703b commit 14f09e4

File tree

1 file changed

+73
-27
lines changed

1 file changed

+73
-27
lines changed

Sources/Algorithms/Chunked.swift

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -392,66 +392,112 @@ extension ChunkedByCount {
392392
guard limit != i else { return nil }
393393

394394
if offset > 0 {
395-
guard limit < i || distance(from: i, to: limit) >= offset else {
396-
return nil
397-
}
398-
return offsetForward(i, offsetBy: offset)
395+
return limit > i
396+
? offsetForward(i, offsetBy: offset, limit: limit)
397+
: offsetForward(i, offsetBy: offset)
399398
} else {
400-
guard limit > i || distance(from: i, to: limit) <= offset else {
401-
return nil
402-
}
403-
return offsetBackward(i, offsetBy: offset)
399+
return limit < i
400+
? offsetBackward(i, offsetBy: offset, limit: limit)
401+
: offsetBackward(i, offsetBy: offset)
404402
}
405403
}
406404

407405
@inlinable
408406
public func index(_ i: Index, offsetBy distance: Int) -> Index {
409407
guard distance != 0 else { return i }
410408

411-
return distance > 0
409+
let idx = distance > 0
412410
? offsetForward(i, offsetBy: distance)
413411
: offsetBackward(i, offsetBy: distance)
412+
guard let index = idx else {
413+
fatalError("Out of bounds")
414+
}
415+
return index
414416
}
415417

416418
@usableFromInline
417-
internal func offsetForward(_ i: Index, offsetBy distance: Int) -> Index {
419+
internal func offsetForward(
420+
_ i: Index, offsetBy distance: Int, limit: Index? = nil
421+
) -> Index? {
418422
assert(distance > 0)
423+
419424
return makeOffsetIndex(
420-
from: i, baseBound: base.endIndex, baseDistance: distance * chunkCount
425+
from: i, baseBound: base.endIndex,
426+
distance: distance, baseDistance: distance * chunkCount,
427+
limit: limit, by: >
421428
)
422429
}
423430

424-
@usableFromInline
425-
internal func offsetBackward(_ i: Index, offsetBy distance: Int) -> Index {
426-
assert(distance < 0)
427-
if i.baseRange.lowerBound == base.endIndex {
431+
// Convenience to compute offset backward base distance.
432+
@inline(__always)
433+
private func computeOffsetBackwardBaseDistance(
434+
_ i: Index, _ distance: Int
435+
) -> Int {
436+
if i == endIndex {
428437
let remainder = base.count%chunkCount
429438
// We have to take it into account when calculating offsets.
430439
if remainder != 0 {
431-
// Distance "minus" one(at this point distance is negative) because we
432-
// need to adjust for the last position that have a variadic(remainder)
433-
// number of elements.
434-
let baseDistance = ((distance + 1) * chunkCount) - remainder
435-
return makeOffsetIndex(
436-
from: i, baseBound: base.startIndex, baseDistance: baseDistance
437-
)
440+
// Distance "minus" one(at this point distance is negative)
441+
// because we need to adjust for the last position that have
442+
// a variadic(remainder) number of elements.
443+
return ((distance + 1) * chunkCount) - remainder
438444
}
439445
}
446+
return distance * chunkCount
447+
}
448+
449+
@usableFromInline
450+
internal func offsetBackward(
451+
_ i: Index, offsetBy distance: Int, limit: Index? = nil
452+
) -> Index? {
453+
assert(distance < 0)
454+
let baseDistance =
455+
computeOffsetBackwardBaseDistance(i, distance)
440456
return makeOffsetIndex(
441-
from: i, baseBound: base.startIndex, baseDistance: distance * chunkCount
457+
from: i, baseBound: base.startIndex,
458+
distance: distance, baseDistance: baseDistance,
459+
limit: limit, by: <
442460
)
443461
}
444462

445463
// Helper to compute index(offsetBy:) index.
446464
@inline(__always)
447465
private func makeOffsetIndex(
448-
from i: Index, baseBound: Base.Index, baseDistance: Int
449-
) -> Index {
450-
let baseStartIdx = base.index(
466+
from i: Index, baseBound: Base.Index, distance: Int, baseDistance: Int,
467+
limit: Index?, by limitFn: (Base.Index, Base.Index) -> Bool
468+
) -> Index? {
469+
let baseIdx = base.index(
451470
i.baseRange.lowerBound, offsetBy: baseDistance,
452471
limitedBy: baseBound
453-
) ?? baseBound
472+
)
473+
474+
if let limit = limit {
475+
if baseIdx == nil {
476+
// If we past the bounds while advancing forward and the
477+
// limit is the `endIndex`, since the computation on base
478+
// don't take into account the remainder, we have to make
479+
// sure that passing the bound was because of the distance
480+
// not just because of a remainder. Special casing is less
481+
// expensive than always use count(which could be O(n) for
482+
// non-random access collection base) to compute the base
483+
// distance taking remainder into account.
484+
if baseDistance > 0 && limit == endIndex {
485+
if self.distance(from: i, to: limit) < distance {
486+
return nil
487+
}
488+
} else {
489+
return nil
490+
}
491+
}
492+
493+
// Checks for the limit.
494+
let baseStartIdx = baseIdx ?? baseBound
495+
if limitFn(baseStartIdx, limit.baseRange.lowerBound) {
496+
return nil
497+
}
498+
}
454499

500+
let baseStartIdx = baseIdx ?? baseBound
455501
let baseEndIdx = base.index(
456502
baseStartIdx, offsetBy: chunkCount, limitedBy: base.endIndex
457503
) ?? base.endIndex

0 commit comments

Comments
 (0)