Skip to content

Commit 3830966

Browse files
committed
[NBKPrimeSieve] Mini rework (#118).
1 parent 81e6bc2 commit 3830966

File tree

3 files changed

+197
-89
lines changed

3 files changed

+197
-89
lines changed

Sources/NBKCoreKit/Models/NBKPrimeSieve.swift

Lines changed: 163 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,30 @@
3636
/// ### Customization
3737
///
3838
/// This sieve offers multiple customization options, which may improve performance.
39-
/// Here's an example that's suitable for numbers up to a billion on a machine where
40-
/// the CPU's L1 data cache is 128 KiB:
39+
/// Here's an example that's suitable for numbers up to a billion on a 64-bit machine
40+
/// where the CPU's L1 data cache is 128 KiB:
4141
///
4242
/// ```swift
43-
/// NBKPrimeSieve(cache: .KiB(128), wheel: .x11, culls: .x31, capacity: 50863957)
43+
/// NBKPrimeSieve(cache: .KiB(128), wheel: .x11, culls: .x67, capacity: 50863957)
4444
/// ```
4545
///
4646
/// - Note: The size of the cache determines the size of each increment.
4747
///
4848
public final class NBKPrimeSieve: CustomStringConvertible {
4949

50+
/// A collection of all the primes that fit in one byte.
51+
///
52+
/// - Note: It contains `54` elements.
53+
///
54+
@usableFromInline static let primes = [
55+
0002, 0003, 0005, 0007, 0011, 0013, 0017, 0019,
56+
0023, 0029, 0031, 0037, 0041, 0043, 0047, 0053,
57+
0059, 0061, 0067, 0071, 0073, 0079, 0083, 0089,
58+
0097, 0101, 0103, 0107, 0109, 0113, 0127, 0131,
59+
0137, 0139, 0149, 0151, 0157, 0163, 0167, 0173,
60+
0179, 0181, 0191, 0193, 0197, 0199, 0211, 0223,
61+
0227, 0229, 0233, 0239, 0241, 0251] as [UInt8]
62+
5063
//=------------------------------------------------------------------------=
5164
// MARK: State
5265
//=------------------------------------------------------------------------=
@@ -77,6 +90,14 @@ public final class NBKPrimeSieve: CustomStringConvertible {
7790
// MARK: Initializers
7891
//=------------------------------------------------------------------------=
7992

93+
/// Creates a new instance and sieves the first page.
94+
///
95+
/// - Note: It uses the default arguments of the main initialization method.
96+
///
97+
@inlinable public convenience init() {
98+
self.init()!
99+
}
100+
80101
/// Creates a new instance and sieves the first page.
81102
///
82103
/// - Parameter cache: A collection of bits used to mark composite numbers.
@@ -87,11 +108,16 @@ public final class NBKPrimeSieve: CustomStringConvertible {
87108
///
88109
/// - Parameter capacity: The prime buffer's minimum capacity.
89110
///
111+
/// - Requires: Each element in `wheel` must exist in `culls`.
112+
///
90113
/// - Note: A page contains `1` odd number per bit in `cache`.
91114
///
92115
/// - Note: The defaults strike a balance between size and performance.
93116
///
94-
public init(cache: Cache = .KiB(32), wheel: Wheel = .x07, culls: Culls = .x11, capacity: Int? = nil) {
117+
public init?(cache: Cache = .KiB(4), wheel: Wheel = .x07, culls: Culls = .x11, capacity: Int? = nil) {
118+
//=--------------------------------------=
119+
guard wheel.primes.last! <= culls.primes.last! else { return nil }
120+
//=--------------------------------------=
95121
self.cache = cache
96122
self.wheel = wheel
97123
self.culls = culls
@@ -147,12 +173,7 @@ extension NBKPrimeSieve {
147173
// mark composites not hit by the wheel
148174
//=--------------------------------------=
149175
let iteration = 1 &+ NBK.PBI.quotient(dividing: NBK.ZeroOrMore(self.limit &>> 1 as UInt), by: NBK.PowerOf2(bitWidth: UInt.self))
150-
151-
for pattern in self.culls.patterns {
152-
var pattern = NBK.CyclicIterator(pattern)!
153-
pattern.formIteration(iteration)
154-
self.cache.sieve(pattern: pattern)
155-
}
176+
self.cache.sieve(patterns: self.culls.patterns, iteration: iteration)
156177
//=--------------------------------------=
157178
// mark composites using prime elements
158179
//=--------------------------------------=
@@ -172,8 +193,8 @@ extension NBKPrimeSieve {
172193
guard product.partialValue <= limit, !product.overflow else { continue }
173194
Swift.assert(start <= product.partialValue)
174195

175-
inner.setIndex(unchecked: Int(bitPattern: self.wheel.indices[Int(bitPattern: multiple % self.wheel.circumference)]))
176-
cache.sieve(from: (product.partialValue &- start) &>> 1 as UInt, stride:{ prime &* inner.next() &>> 1 as UInt }) // OK
196+
inner.setIndex(unchecked: Int(bitPattern: self.wheel.indices[Int(bitPattern: multiple % self.wheel.circumference)] ))
197+
self.cache.sieve(from: (product.partialValue &- start) &>> 1 as UInt, stride:{ prime &* inner.next() &>> 1 as UInt }) // OK
177198
}
178199
//=--------------------------------------=
179200
Self.commit(&self.cache, to: &self.state)
@@ -198,15 +219,15 @@ extension NBKPrimeSieve {
198219
state.elements.append(position)
199220
}
200221

201-
cache.base[index] = chunk.onesComplement()
222+
cache.base[index] = UInt.max
202223
state.limit &+= UInt(bitPattern: UInt.bitWidth) &<< 1 as UInt
203224
}
204225
}
205226

206227
@inline(never) @inlinable static func makeInitialState(_ cache: inout Cache, _ wheel: Wheel, _ culls: Culls, _ capacity: Int?) -> State {
207228
Swift.assert(wheel.primes.first == 00000000000000002)
208229
Swift.assert(culls.primes.first != 00000000000000002)
209-
precondition(wheel.primes.last! <= culls.primes.last!, "must cull multiples of each element in wheel")
230+
Swift.assert(wheel.primes.last! <= culls.primes.last!, "must cull multiples of each element in wheel")
210231
Swift.assert(wheel.primes[1...].allSatisfy(culls.primes.contains))
211232
Swift.assert(cache.base.allSatisfy { $0.onesComplement().isZero })
212233
//=--------------------------------------=
@@ -222,10 +243,7 @@ extension NBKPrimeSieve {
222243
// mark each number in: 1, culls
223244
//=--------------------------------------=
224245
cache.base[cache.base.startIndex] = ~1
225-
226-
for pattern in culls.patterns {
227-
cache.sieve(pattern: NBK.CyclicIterator(pattern)!)
228-
}
246+
cache.sieve(patterns: culls.patterns)
229247
//=--------------------------------------=
230248
// mark each number in: composites
231249
//=--------------------------------------=
@@ -366,28 +384,37 @@ extension NBKPrimeSieve {
366384
// MARK: Transformations
367385
//=--------------------------------------------------------------------=
368386

369-
/// Sieves the `marks` with the given pattern `cycle`.
370-
@inlinable mutating func sieve(pattern cycle: NBK.CyclicIterator<[UInt]>) {
371-
var cycle = cycle; for index in self.base.indices { self.base[index] &= cycle.next() }
387+
/// Sieves this instance using cyclical bit `patterns` from `iteration`.
388+
@inlinable mutating func sieve(patterns: [[UInt]], iteration: UInt = 0) {
389+
self.base.withUnsafeMutableBufferPointer { base in
390+
for var pattern in patterns.lazy.map({ NBK.CyclicIterator($0)! }) {
391+
pattern.formIteration(iteration)
392+
for index in base.indices {
393+
base[index] &= pattern.next()
394+
}
395+
}
396+
}
372397
}
373398

374-
/// Sieves the `marks` from `start` with strides of `increment`.
399+
/// Sieves this instance from `start` with strides of `increment`.
375400
///
376401
/// - Requires: `increment() <= UInt.max - UInt.bitWidth + 1`
377402
///
378403
@inlinable mutating func sieve(from start: UInt, stride increment: () -> UInt) {
379-
var index = NBK.PBI.dividing(NBK.ZeroOrMore(start), by: NBK.PowerOf2(bitWidth: UInt.self))
380-
while index.quotient < UInt(bitPattern: self.base.count) {
381-
var chunk = UInt.zero
382-
383-
while index.remainder < UInt.bitWidth {
384-
chunk &+= 1 &<< index.remainder
385-
index.remainder &+= increment()
404+
self.base.withUnsafeMutableBufferPointer { base in
405+
var ((index)): QR<UInt,UInt> = NBK.PBI.dividing(NBK.ZeroOrMore(start), by: NBK.PowerOf2(bitWidth: UInt.self))
406+
while index.quotient < UInt(bitPattern: base.count) {
407+
var chunk = UInt.zero
408+
409+
while index.remainder < UInt(bitPattern: UInt.bitWidth) {
410+
chunk &+= 1 &<< index.remainder
411+
index.remainder &+= increment()
412+
}
413+
414+
base[Int(bitPattern: index.quotient)] &= chunk.onesComplement()
415+
index.quotient &+= NBK.PBI .quotient(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
416+
index.remainder = NBK.PBI.remainder(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
386417
}
387-
388-
self.base[Int(bitPattern: index.quotient)] &= chunk.onesComplement()
389-
index.quotient &+= NBK.PBI .quotient(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
390-
index.remainder = NBK.PBI.remainder(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
391418
}
392419
}
393420
}
@@ -406,29 +433,44 @@ extension NBKPrimeSieve {
406433
/// A cyclical pattern used to skip small prime multiples.
407434
@frozen public struct Wheel {
408435

409-
/// A wheel formed by the sequence: 2.
436+
/// A wheel formed by the sequence: `2`.
437+
///
438+
/// - Note: It covers `2` elements in `1` increment.
439+
///
410440
@inlinable public static var x02: Self {
411-
Self(primes: [2])
441+
Self(primes: NBKPrimeSieve.primes[...0])
412442
}
413443

414-
/// A wheel formed by the sequence: 2, 3.
444+
/// A wheel formed by the sequence: `2`, `3`.
445+
///
446+
/// - Note: It covers `6` elements in `2` increments.
447+
///
415448
@inlinable public static var x03: Self {
416-
Self(primes: [2, 3])
449+
Self(primes: NBKPrimeSieve.primes[...1])
417450
}
418451

419-
/// A wheel formed by the sequence: 2, 3, 5.
452+
/// A wheel formed by the sequence: `2`, `3`, `5`.
453+
///
454+
/// - Note: It covers `30` elements in `8` increments.
455+
///
420456
@inlinable public static var x05: Self {
421-
Self(primes: [2, 3, 5])
457+
Self(primes: NBKPrimeSieve.primes[...2])
422458
}
423459

424-
/// A wheel formed by the sequence: 2, 3, 5, 7.
460+
/// A wheel formed by the sequence: `2`, `3`, `5`, `7`.
461+
///
462+
/// - Note: It covers `210` elements in `48` increments.
463+
///
425464
@inlinable public static var x07: Self {
426-
Self(primes: [2, 3, 5, 7])
465+
Self(primes: NBKPrimeSieve.primes[...3])
427466
}
428467

429-
/// A wheel formed by the sequence: 2, 3, 5, 7, 11.
468+
/// A wheel formed by the sequence: `2`, `3`, `5`, `7`, `11`.
469+
///
470+
/// - Note: It covers `2310` elements in `480` increments.
471+
///
430472
@inlinable public static var x11: Self {
431-
Self(primes: [2, 3, 5, 7, 11])
473+
Self(primes: NBKPrimeSieve.primes[...4])
432474
}
433475

434476
//=--------------------------------------------------------------------=
@@ -490,6 +532,10 @@ extension NBKPrimeSieve {
490532
// MARK: Initializers
491533
//=--------------------------------------------------------------------=
492534

535+
@inlinable init(primes: [UInt8].SubSequence) {
536+
self.init(primes: primes.map(UInt.init(truncatingIfNeeded:)))
537+
}
538+
493539
@usableFromInline init(primes: [UInt]) {
494540
//=----------------------------------=
495541
Swift.assert(primes.allSatisfy({ $0 >= 2 }))
@@ -508,7 +554,11 @@ extension NBKPrimeSieve {
508554
let count = Int(bitPattern: 1 + value) - self.values.count
509555
self.values .append(contentsOf: repeatElement(value, count: count))
510556
self.indices.append(contentsOf: repeatElement(self.increments.count, count: count))
511-
}; self.increments.append(self.circumference - self.values.last! + self.values.first!)
557+
}
558+
559+
let last = self.values.last! // default to zero if empty is allowed
560+
let first = self.values.first! // default to zero if empty is allowed
561+
self.increments.append(self.circumference - last + first)
512562
}
513563
}
514564
}
@@ -524,41 +574,81 @@ extension NBKPrimeSieve {
524574
//*========================================================================*
525575

526576
/// A collection of cyclical patterns used to cull multiples of small primes.
577+
///
578+
/// - Note: Even numbers are culled by omission, not by bit pattern.
579+
///
527580
@frozen public struct Culls {
528581

529-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11.
530-
@inlinable public static var x11: Self {
531-
Self(primes: [3, 5, 7, 11])
582+
/// Cyclical bit patterns for multiples of primes from `3` through `5`.
583+
///
584+
/// - Note: It stores `15` elements and culls `2` primes in `1` loop.
585+
///
586+
@inlinable public static var x05: Self {
587+
Self(primes: NBKPrimeSieve.primes[1...02])
532588
}
533589

534-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13.
535-
@inlinable public static var x13: Self {
536-
Self(primes: [3, 5, 7, 11, 13])
590+
/// Cyclical bit patterns for multiples of primes from `3` through `11`.
591+
///
592+
/// - Note: It stores `68` elements and culls `4` primes in `2` loops.
593+
///
594+
@inlinable public static var x11: Self {
595+
Self(primes: NBKPrimeSieve.primes[1...04])
537596
}
538597

539-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17.
598+
/// Cyclical bit patterns for multiples of primes from `3` through `17`.
599+
///
600+
/// - Note: It stores `193` elements and culls `6` primes in `3` loops.
601+
///
540602
@inlinable public static var x17: Self {
541-
Self(primes: [3, 5, 7, 11, 13, 17])
603+
Self(primes: NBKPrimeSieve.primes[1...06])
542604
}
543605

544-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19.
545-
@inlinable public static var x19: Self {
546-
Self(primes: [3, 5, 7, 11, 13, 17, 19])
606+
/// Cyclical bit patterns for multiples of primes from `3` through `23`.
607+
///
608+
/// - Note: It stores `426` elements and culls `8` primes in `4` loops.
609+
///
610+
@inlinable public static var x23: Self {
611+
Self(primes: NBKPrimeSieve.primes[1...08])
547612
}
548613

549-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23.
550-
@inlinable public static var x23: Self {
551-
Self(primes: [3, 5, 7, 11, 13, 17, 19, 23])
614+
/// Cyclical bit patterns for multiples of primes from `3` through `31`.
615+
///
616+
/// - Note: It stores `829` elements and culls `10` primes in `5` loops.
617+
///
618+
@inlinable public static var x31: Self {
619+
Self(primes: NBKPrimeSieve.primes[1...10])
552620
}
553621

554-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23, 29.
555-
@inlinable public static var x29: Self {
556-
Self(primes: [3, 5, 7, 11, 13, 17, 19, 23, 29])
622+
/// Cyclical bit patterns for multiples of primes from `3` through `41`.
623+
///
624+
/// - Note: It stores `1466` elements and culls `12` primes in `6` loops.
625+
///
626+
@inlinable public static var x41: Self {
627+
Self(primes: NBKPrimeSieve.primes[1...12])
557628
}
558629

559-
/// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23, 29, 31.
560-
@inlinable public static var x31: Self {
561-
Self(primes: [3, 5, 7, 11, 13, 17, 19, 23, 29, 31])
630+
/// Cyclical bit patterns for multiples of primes from `3` through `47`.
631+
///
632+
/// - Note: It stores `2383` elements and culls `14` primes in `7` loops.
633+
///
634+
@inlinable public static var x47: Self {
635+
Self(primes: NBKPrimeSieve.primes[1...14])
636+
}
637+
638+
/// Cyclical bit patterns for multiples of primes from `3` through `59`.
639+
///
640+
/// - Note: It stores `3662` elements and culls `16` primes in `8` loops.
641+
///
642+
@inlinable public static var x59: Self {
643+
Self(primes: NBKPrimeSieve.primes[1...16])
644+
}
645+
646+
/// Cyclical bit patterns for multiples of primes from `3` through `67`.
647+
///
648+
/// - Note: It stores `5373` elements and culls `18` primes in `9` loops.
649+
///
650+
@inlinable public static var x67: Self {
651+
Self(primes: NBKPrimeSieve.primes[1...18])
562652
}
563653

564654
//=--------------------------------------------------------------------=
@@ -580,6 +670,10 @@ extension NBKPrimeSieve {
580670
// MARK: Initializers
581671
//=--------------------------------------------------------------------=
582672

673+
@inlinable init(primes: [UInt8].SubSequence) {
674+
self.init(primes: primes.map(UInt.init(truncatingIfNeeded:)))
675+
}
676+
583677
@inlinable init(primes: [UInt]) {
584678
self.primes = primes
585679
self.patterns = Culls.patterns(primes: self.primes)
@@ -591,9 +685,13 @@ extension NBKPrimeSieve {
591685

592686
/// Patterns grow multiplicatively, so chunking reduces memory cost.
593687
///
594-
/// g([3, 5, 7, 11, 13]) -> [f([3, 13]), f([5, 11]), f([7])]
688+
/// g([3, 5, 7, 11]) -> [f([3, 11]), f([5, 7])]
689+
///
690+
/// - Requires: The number of primes should be even for performance reasons.
595691
///
596692
@usableFromInline static func patterns(primes: [UInt]) -> [[UInt]] {
693+
Swift.assert(primes.count.isEven)
694+
597695
var patterns = [[UInt]]()
598696
var lhsIndex = primes.startIndex
599697
var rhsIndex = primes.index(before: primes.endIndex)
@@ -604,10 +702,6 @@ extension NBKPrimeSieve {
604702
patterns.formIndex(before: &rhsIndex)
605703
}
606704

607-
if lhsIndex == rhsIndex {
608-
patterns.append(self.pattern(primes: [primes[lhsIndex]]))
609-
}
610-
611705
return patterns as [[UInt]]
612706
}
613707

0 commit comments

Comments
 (0)