Skip to content

Commit

Permalink
NBKPrimeSieve (#114)
Browse files Browse the repository at this point in the history
- I made the Sieve of Eratosthenes 60% faster by using [UInt] as a bit set.
  • Loading branch information
oscbyspro committed Nov 25, 2023
1 parent 3620027 commit 0d7fbc7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 14 deletions.
75 changes: 69 additions & 6 deletions Sources/NBKCoreKit/Models/NBKPrimeSieve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
public init(through limit: UInt) {
self._limit = limit
self._elements = []
Self.primesByEratosthenes(through: limit, appending: &self._elements)
Self.primesByEratosthenesBitSet(through: limit, appending: &self._elements)
}

//*========================================================================*
Expand All @@ -63,22 +63,23 @@ extension NBKPrimeSieve {
///
/// [sieve]: https://en.wikipedia.org/wiki/sieve_of_eratosthenes
///
/// - Complexity: O(n × log(log(n)))
/// - Complexity: O(limit × log(log(limit)))
///
/// - Note: 1 million takes 0.0s on MacBook Pro (13-inch, M1, 2020).
///
/// - Note: 1 billion takes 3.6s on MacBook Pro (13-inch, M1, 2020).
///
@inlinable static func primesByEratosthenes(through limit: UInt, appending elements: inout some RangeReplaceableCollection<UInt>) {
static func primesByEratosthenes(through limit: UInt, appending elements: inout [UInt]) {
//=--------------------------------------=
if limit < 2 { return }
if limit < 2 as UInt { return }
//=--------------------------------------=
// mark each number in: 3, 5, 7, 9, ...
//=--------------------------------------=
let count = Int(bitPattern: (limit &- 1) &>> 1)
var marks = Array(repeating: true, count: count) // UInt.max at Int.max - 1

var value = 3 as UInt; var index: Int; loop: while true { defer { value &+= 2 }
var value = 3 as UInt; loop: while true {
var index: Int; defer { value &+= 2 }

let (first, overflow) = value.multipliedReportingOverflow(by: value)

Expand All @@ -98,7 +99,69 @@ extension NBKPrimeSieve {
}

for index in marks.indices where marks[index] {
elements.append(UInt(bitPattern: index) &<< 1 &+ 3)
elements.append(UInt(bitPattern: index) &<< 1 &+ 3) // UInt.max at Int.max - 1
}
}

/// An adaptation of [the Sieve of Eratosthenes][sieve].
///
/// [sieve]: https://en.wikipedia.org/wiki/sieve_of_eratosthenes
///
/// - Complexity: O(limit × log(log(limit)))
///
/// - Note: 1 million takes 0.0s on MacBook Pro (13-inch, M1, 2020).
///
/// - Note: 1 billion takes 2.2s on MacBook Pro (13-inch, M1, 2020).
///
static func primesByEratosthenesBitSet(through limit: UInt, appending elements: inout [UInt]) {
//=--------------------------------------=
if limit < 2 as UInt { return }
//=--------------------------------------=
var value : UInt
let split = NBK.PBI.dividing(NBK.ZeroOrMore((limit &- 1) &>> 1), by: NBK.PowerOf2(bitWidth: UInt.self))
var marks = Array(repeating: UInt.max, count: Int(split.quotient + UInt(bit: !split.remainder.isZero)))
//=--------------------------------------=
// mark each number in: 3, 5, 7, 9, ...
//=--------------------------------------=
value = 3 as UInt; while true { var index: QR<UInt,UInt>; defer { value &+= 2 }

let (first, overflow) = value.multipliedReportingOverflow(by: value)

done: if first > limit || overflow { break }

index = NBK.PBI.dividing(NBK.ZeroOrMore(value &>> 1 &- 1), by: NBK.PowerOf2(bitWidth: UInt.self))

composite: if (marks[Int(bitPattern: index.quotient)] & (1 &<< index.remainder)).isZero { continue }

index = NBK.PBI.dividing(NBK.ZeroOrMore(first &>> 1 &- 1), by: NBK.PowerOf2(bitWidth: UInt.self))

composite: while index.quotient < UInt(bitPattern: marks.count) {
var mask = UInt.zero; while index.remainder < UInt.bitWidth { mask &+= 1 &<< index.remainder; index.remainder &+= value }
marks[Int(bitPattern: index.quotient)] &= mask.onesComplement()
index.quotient &+= NBK.PBI .quotient(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
index.remainder = NBK.PBI.remainder(dividing: NBK.ZeroOrMore(index.remainder), by: NBK.PowerOf2(bitWidth: UInt.self))
}
}
//=--------------------------------------=
// add each un/marked number to the list
//=--------------------------------------=
unmarked: do {
elements.append(2 as UInt)
}

value = 3 as UInt; loop: for var chunk in marks { var position = value

while !chunk.isZero {
let shift = UInt(bitPattern: chunk.trailingZeroBitCount)
if position.addReportingOverflow(shift &<< 1) || position > limit { break loop }

elements.append(position)

if position.addReportingOverflow(00000000002) || position > limit { break loop }
chunk >>= shift &+ 1 // smart shifting because it may exceed the bit width
}

if (value).addReportingOverflow(UInt(bitPattern: UInt.bitWidth) &<< 1) { break loop }
}
}
}
14 changes: 7 additions & 7 deletions Tests/NBKCoreKitBenchmarks/Models/NBKPrimeSieve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,27 @@ final class NBKPrimeSieveBenchmarks: XCTestCase {
//=------------------------------------------------------------------------=

func testNoLoopFirst1000() {
NBK.blackHole(T(first: .thousand))
XCTAssertEqual(T(first: .thousand).elements.count, 1000)
}

func testNoLoopFirst1000000() {
NBK.blackHole(T(first: .million))
XCTAssertEqual(T(first: .million).elements.count, 1000000)
}

func testNoLoop1E6() {
NBK.blackHole(T(through: 1000000))
XCTAssertEqual(T(through: 0001000000).elements.count, 00078498)
}

func testNoLoop1E7() {
NBK.blackHole(T(through: 10000000))
XCTAssertEqual(T(through: 0010000000).elements.count, 00664579)
}

func testNoLoop1E8() {
NBK.blackHole(T(through: 100000000))
XCTAssertEqual(T(through: 0100000000).elements.count, 05761455)
}

func testNoLoop1E9() {
NBK.blackHole(T(through: 1000000000))
XCTAssertEqual(T(through: 1000000000).elements.count, 50847534)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/NBKCoreKitTests/Models/NBKPrimeSieve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ final class NBKPrimeSieveTests: XCTestCase {
check(result, limit: Self.primes[999], elements: Self.primes[...999])
}

#if !DEBUG // fast in RELEASE, slow in DEBUG
#if !DEBUG // fast in RELEASE, too slow in DEBUG
brr: do {
let result = T(first: .million)
XCTAssertEqual(result.elements.count, 01000000)
Expand Down

0 comments on commit 0d7fbc7

Please sign in to comment.