diff --git a/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift b/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift index 14025692..8454a918 100644 --- a/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift +++ b/Sources/NBKCoreKit/Models/NBKPrimeSieve.swift @@ -31,7 +31,6 @@ /// } /// ``` /// -/// public final class NBKPrimeSieve: CustomStringConvertible { //=------------------------------------------------------------------------= @@ -48,7 +47,7 @@ public final class NBKPrimeSieve: CustomStringConvertible { /// @usableFromInline var state: State - /// A collection of bits represeted by a collection of words. + /// A finite sequence of odd numbers represented by a collection of bits. /// /// - Requires: Each bit must be set at the start and end of each access. /// @@ -65,14 +64,22 @@ public final class NBKPrimeSieve: CustomStringConvertible { //=------------------------------------------------------------------------= /// Creates a new instance and sieves the first page. + /// + /// - Parameter cache: A collection of bits used to mark composite numbers. + /// + /// - Parameter wheel: A cyclical pattern used to skip multiples of small primes. + /// + /// - Parameter culls: A collection of cyclical patterns used to cull multiples of small primes. + /// + /// - Note: A page contains `1` odd number per bit in `cache`. /// - /// - Note: A page contains `1` odd number per bit in `size`. + /// - Note: The defaults strike balance between size and performance. /// - public init(size: Size) { - self.cache = Cache(size: size) - self.wheel = Wheel(primes:[2, 3, 5, 7, 11]) - self.culls = Culls(primes:[ 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]) - self.state = Self.makeInitialState(&cache, wheel, culls) + public init(cache: Cache = .KiB(32), wheel: Wheel = .x07, culls: Culls = .x11) { + self.cache = cache + self.wheel = wheel + self.culls = culls + self.state = Self.makeInitialState(&self.cache, self.wheel, self.culls) } //=------------------------------------------------------------------------= @@ -101,63 +108,6 @@ public final class NBKPrimeSieve: CustomStringConvertible { public var description: String { "\(Self.self)(limit: \(self.limit), count: \(self.elements.count))" } - - //*========================================================================* - // MARK: * Size - //*========================================================================* - - /// The size of the sieve. - /// - /// Larger sieves evaluate more numbers per page and use more memory. - /// - /// - Note: The first KiB yields all 1900 prime numbers through 16383. - /// - /// - Note: The CPU's L1 data cache is the sweet spot (128 KiB on Apple M1). - /// - /// ### Page Alignment - /// - /// The last page near will overflow unless the page ends at `UInt.max`. - /// - /// - Note: All values can be sieved when the page size is a power of two. - /// - @frozen public struct Size { - - //=--------------------------------------------------------------------= - // MARK: State - //=--------------------------------------------------------------------= - - /// The number of words per page. - /// - /// It represents at most `Int.max` bits so the odd number stride fits. - /// - /// - Requires: `2 * UInt.bitWidth * words <= UInt.max` - /// - @usableFromInline let words: Int - - //=--------------------------------------------------------------------= - // MARK: Initializers - //=--------------------------------------------------------------------= - - @usableFromInline init(@NBK.MoreThanZero words: Int) { - self.words = words - precondition(words <= (Int.max / UInt.bitWidth), - "the prime sieve's increment must fit in UInt") - } - - //=--------------------------------------------------------------------= - // MARK: Initializers - //=--------------------------------------------------------------------= - - /// The size per page measured in KiB (i.e. 1024 B). - @inlinable public static func KiB(_ count: Int) -> Self { - Self(words: count * (8 * 1024 / UInt.bitWidth)) - } - - /// The size per page measured in words. - @inlinable public static func words(_ count: Int) -> Self { - Self(words: count) - } - } } //=----------------------------------------------------------------------------= @@ -241,6 +191,7 @@ extension NBKPrimeSieve { @inline(never) @inlinable static func makeInitialState(_ cache: inout Cache, _ wheel: Wheel, _ culls: Culls) -> State { Swift.assert(wheel.primes.first == 000000000000000002) Swift.assert(culls.primes.first != 000000000000000002) + precondition(wheel.primes.last! <= culls.primes.last!, "must cull each element in wheel") Swift.assert(wheel.primes[1...].allSatisfy(culls.primes.contains)) Swift.assert(cache.base.allSatisfy { $0.onesComplement().isZero }) //=--------------------------------------= @@ -322,11 +273,31 @@ extension NBKPrimeSieve { // MARK: * Cache //*========================================================================* - /// ### Development + /// A finite sequence of odd numbers represented by a collection of bits. + /// + /// Larger sieves evaluate more numbers per page and use more memory. + /// + /// - Note: The first KiB yields all 1900 prime numbers through 16383. + /// + /// - Note: The CPU's L1 data cache is the sweet spot (128 KiB on Apple M1). /// - /// Size overflow is prevented by the size model's preconditions. + /// ### Alignment /// - @frozen @usableFromInline struct Cache { + /// The last page will overflow unless the last page ends at `UInt.max`. + /// + /// - Note: All values can be sieved when its size is a power of two. + /// + @frozen public struct Cache { + + /// The size per page measured in KiB (i.e. 1024 B). + @inlinable public static func KiB(_ count: Int) -> Self { + Self(words: count * (8 * 1024 / UInt.bitWidth)) + } + + /// The size per page measured in words. + @inlinable public static func words(_ count: Int) -> Self { + Self(words: count) + } //=--------------------------------------------------------------------= // MARK: State @@ -338,8 +309,16 @@ extension NBKPrimeSieve { // MARK: Initializers //=--------------------------------------------------------------------= - @inlinable init(size: Size) { - self.base = Array(repeating: UInt.max, count: size.words) + /// ### Development + /// + /// It fits at most `Int.max` bits so the odd number stride fits. + /// + /// - Requires: `2 * UInt.bitWidth * words <= UInt.max` + /// + @inlinable init(words: Int) { + precondition(words <= (Int.max / UInt.bitWidth), + "the prime sieve's increment must fit in UInt") + self.base = Array(repeating: UInt.max, count: words) } //=--------------------------------------------------------------------= @@ -399,7 +378,32 @@ extension NBKPrimeSieve { //*========================================================================* /// A cyclical pattern used to skip small prime multiples. - @frozen @usableFromInline struct Wheel { + @frozen public struct Wheel { + + /// A wheel formed by the sequence: 2. + @inlinable public static var x02: Self { + Self(primes: [2]) + } + + /// A wheel formed by the sequence: 2, 3. + @inlinable public static var x03: Self { + Self(primes: [2, 3]) + } + + /// A wheel formed by the sequence: 2, 3, 5. + @inlinable public static var x05: Self { + Self(primes: [2, 3, 5]) + } + + /// A wheel formed by the sequence: 2, 3, 5, 7. + @inlinable public static var x07: Self { + Self(primes: [2, 3, 5, 7]) + } + + /// A wheel formed by the sequence: 2, 3, 5, 7, 11. + @inlinable public static var x11: Self { + Self(primes: [2, 3, 5, 7, 11]) + } //=--------------------------------------------------------------------= // MARK: State @@ -494,7 +498,42 @@ extension NBKPrimeSieve { //*========================================================================* /// A collection of cyclical patterns used to cull multiples of small primes. - @frozen @usableFromInline struct Culls { + @frozen public struct Culls { + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11. + @inlinable public static var x11: Self { + Self(primes: [3, 5, 7, 11]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13. + @inlinable public static var x13: Self { + Self(primes: [3, 5, 7, 11, 13]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17. + @inlinable public static var x17: Self { + Self(primes: [3, 5, 7, 11, 13, 17]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19. + @inlinable public static var x19: Self { + Self(primes: [3, 5, 7, 11, 13, 17, 19]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23. + @inlinable public static var x23: Self { + Self(primes: [3, 5, 7, 11, 13, 17, 19, 23]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23, 29. + @inlinable public static var x29: Self { + Self(primes: [3, 5, 7, 11, 13, 17, 19, 23, 29]) + } + + /// Cyclical bit patterns for multiples of: 3, 5, 7, 11, 13, 17, 19, 23, 29, 31. + @inlinable public static var x31: Self { + Self(primes: [3, 5, 7, 11, 13, 17, 19, 23, 29, 31]) + } //=--------------------------------------------------------------------= // MARK: State diff --git a/Tests/NBKCoreKitBenchmarks/Models/NBKPrimeSieve.swift b/Tests/NBKCoreKitBenchmarks/Models/NBKPrimeSieve.swift index 02cddd17..2cb2ee22 100644 --- a/Tests/NBKCoreKitBenchmarks/Models/NBKPrimeSieve.swift +++ b/Tests/NBKCoreKitBenchmarks/Models/NBKPrimeSieve.swift @@ -25,19 +25,19 @@ final class NBKPrimeSieveBenchmarks: XCTestCase { //=------------------------------------------------------------------------= func testLimit1E6() { - let ((sieve)) = T(size: .KiB(128)) + let ((sieve)) = T(cache: .KiB(128), wheel: .x11, culls: .x31) while sieve.limit < 1000000 { ((sieve)).increment() } - + XCTAssertEqual(sieve.limit, 2097151) XCTAssertEqual(sieve.elements.last!, 2097143) XCTAssertEqual(sieve.elements.count, 0155611) } func testLimit1E7() { - let ((sieve)) = T(size: .KiB(128)) + let ((sieve)) = T(cache: .KiB(128), wheel: .x11, culls: .x31) while sieve.limit < 10000000 { ((sieve)).increment() @@ -49,7 +49,7 @@ final class NBKPrimeSieveBenchmarks: XCTestCase { } func testLimit1E8() { - let ((sieve)) = T(size: .KiB(128)) + let ((sieve)) = T(cache: .KiB(128), wheel: .x11, culls: .x31) while sieve.limit < 100000000 { ((sieve)).increment() @@ -61,7 +61,7 @@ final class NBKPrimeSieveBenchmarks: XCTestCase { } func testLimit1E9() { - let ((sieve)) = T(size: .KiB(128)) + let ((sieve)) = T(cache: .KiB(128), wheel: .x11, culls: .x31) while sieve.limit < 1000000000 { ((sieve)).increment() diff --git a/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift b/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift index a3538c4c..68e36b22 100644 --- a/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift +++ b/Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift @@ -152,7 +152,7 @@ final class NBKPrimeSieveTests: XCTestCase { //=------------------------------------------------------------------------= func testMinSize() { - let ((sieve)) = T(size: .words(1)) + let ((sieve)) = T(cache: .words(1)) while sieve.limit < 0127 { ((sieve)).increment() } @@ -176,7 +176,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testBit064() { - let ((sieve)) = T(size: .words( 0064 / UInt.bitWidth)) + let ((sieve)) = T(cache: .words( 0064 / UInt.bitWidth)) check(sieve, limit: 0127, count: 0031, last: 127) sieve.increment() @@ -190,7 +190,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testBit128() { - let ((sieve)) = T(size: .words( 0128 / UInt.bitWidth)) + let ((sieve)) = T(cache: .words( 0128 / UInt.bitWidth)) check(sieve, limit: 0255, count: 0054, last: 0251) sieve.increment() @@ -204,7 +204,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testBit192() { - let ((sieve)) = T(size: .words( 0192 / UInt.bitWidth)) + let ((sieve)) = T(cache: .words( 0192 / UInt.bitWidth)) check(sieve, limit: 0383, count: 0076, last: 0383) sieve.increment() @@ -218,7 +218,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testBit256() { - let ((sieve)) = T(size: .words( 0256 / UInt.bitWidth)) + let ((sieve)) = T(cache: .words( 0256 / UInt.bitWidth)) check(sieve, limit: 0511, count: 0097, last: 0509) sieve.increment() @@ -232,7 +232,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testKiB001() { - let ((sieve)) = T(size: .KiB(001)) + let ((sieve)) = T(cache: .KiB(001)) check(sieve, limit: 016383, count: 01900, last: 016381) sieve.increment() @@ -246,7 +246,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testKiB002() { - let ((sieve)) = T(size: .KiB(002)) + let ((sieve)) = T(cache: .KiB(002)) check(sieve, limit: 032767, count: 03512, last: 032749) sieve.increment() @@ -260,7 +260,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testKiB003() { - let ((sieve)) = T(size: .KiB(003)) + let ((sieve)) = T(cache: .KiB(003)) check(sieve, limit: 049151, count: 05051, last: 049139) sieve.increment() @@ -274,7 +274,7 @@ final class NBKPrimeSieveTests: XCTestCase { } func testKiB004() { - let ((sieve)) = T(size: .KiB(004)) + let ((sieve)) = T(cache: .KiB(004)) check(sieve, limit: 065535, count: 06542, last: 065521) sieve.increment() @@ -287,6 +287,29 @@ final class NBKPrimeSieveTests: XCTestCase { check(sieve, limit: 262143, count: 23000, last: 262139) } + //=------------------------------------------------------------------------= + // MARK: Test x Settings + //=------------------------------------------------------------------------= + + func testSettings() { + let/**/ cache: T.Cache = .words( 256 / UInt.bitWidth) + for/**/ wheel: T.Wheel in [.x02, .x03, .x05, .x07, .x11] { + for culls: T.Culls in [.x11, .x13, .x17, .x19, .x23, .x29, .x31] { + let ((sieve)) = T(cache: cache, wheel: wheel, culls: culls) + check(sieve, limit: 0511, count: 0097, last: 0509) + + sieve.increment() + check(sieve, limit: 1023, count: 0172, last: 1021) + + sieve.increment() + check(sieve, limit: 1535, count: 0242, last: 1531) + + sieve.increment() + check(sieve, limit: 2047, count: 0309, last: 2039) + } + } + } + //=------------------------------------------------------------------------= // MARK: Utilities //=------------------------------------------------------------------------=