11// ---------------------------------------------------------------------------
2- // PrimeCPP.cpp : Pol Marcet's Modified version of Dave's Garage Prime Sieve
3- // Some great ideas taken from Rust's implementation from Michael Barber
4- // @mike-barber https://www.github.com/mike-barber (bit-storage-rotate)
2+ // PrimeCPP.cpp : Optimized version of Dave's Garage Prime Sieve
3+ // Optimization: Use a BitArray that represents only odd numbers to reduce memory usage
4+ // and improve cache performance.
55// ---------------------------------------------------------------------------
66
77#include < chrono>
@@ -26,83 +26,64 @@ const uint64_t DEFAULT_UPPER_LIMIT = 10'000'000LLU;
2626class BitArray {
2727 uint8_t *array;
2828 size_t arrSize;
29+ size_t logicalSize;
2930
30- inline static size_t arraySize (size_t size)
31+ static constexpr size_t arraySize (size_t size)
3132 {
3233 return (size >> 3 ) + ((size & 7 ) > 0 );
3334 }
3435
35- inline static size_t index (size_t n)
36+ static constexpr size_t index (size_t n)
3637 {
3738 return (n >> 3 );
3839 }
3940
40- inline static uint8_t getSubindex (size_t n, uint8_t d)
41- {
42- return d & uint8_t (uint8_t (0x01 ) << (n % 8 ));
43- }
44-
45- inline void setFalseSubindex (size_t n, uint8_t &d)
46- {
47- d &= ~uint8_t (uint8_t (0x01 ) << (n % 8 ));
48- }
49-
5041public:
51- explicit BitArray (size_t size) : arrSize (size)
42+ explicit BitArray (size_t size) : logicalSize (size)
5243 {
53- array = new uint8_t [arraySize (size)];
54- std::memset (array, 0xFF , arraySize (size));
44+ arrSize = (size + 1 ) / 2 ; // Only store bits for odd numbers
45+ array = new uint8_t [arraySize (arrSize)];
46+ // Bits are left at zero default, so no need to initialize them
47+ // std::memset(array, 0x00, arraySize(arrSize));
5548 }
5649
5750 ~BitArray () { delete[] array; }
5851
5952 bool get (size_t n) const
6053 {
61- return (array[index (n)] & (uint8_t (1 ) << (n % 8 ))) != 0 ;
54+ if (n % 2 == 0 )
55+ return false ; // Even numbers > 2 are not prime
56+ n = n / 2 ; // Map the actual number to the index in the array
57+ return !(array[index (n)] & (uint8_t (1 ) << (n % 8 )));
6258 }
6359
64- static constexpr uint8_t rol ( uint8_t x, uint8_t n)
60+ void set ( size_t n)
6561 {
66- n %= 8 ;
67- if (n == 0 )
68- return x;
69- else
70- return (x << n) | (x >> (8 - n));
62+ n = n / 2 ; // Map the actual number to the index in the array
63+ array[index (n)] |= (uint8_t (1 ) << (n % 8 ));
7164 }
7265
73- void setFlagsFalse (size_t n, size_t skip)
74- {
75- auto rolling_mask = ~uint8_t (1 << n % 8 );
76- auto roll_bits = skip % 8 ;
77- while (n < arrSize) {
78- array[index (n)] &= rolling_mask;
79- n += skip;
80- if (roll_bits != 0 )
81- rolling_mask = rol (rolling_mask, roll_bits);
82- }
83- }
84-
8566 inline size_t size () const
8667 {
87- return arrSize ;
68+ return logicalSize ;
8869 }
8970};
9071
9172
9273// prime_sieve
9374//
94- // Represents the data comprising the sieve (an array of N bits, where N is the upper limit prime being tested )
95- // as well as the code needed to eliminate non-primes from its array, which you perform by calling runSieve.
75+ // Represents the data comprising the sieve (an array of bits representing odd numbers starting from 3 )
76+ // and includes the code needed to eliminate non-primes from its array by calling runSieve.
9677
9778class prime_sieve
9879{
9980 private:
10081
101- BitArray Bits; // Sieve data, where 1 ==prime, 0 ==not
82+ BitArray Bits; // Sieve data, where 0 ==prime, 1 ==not
10283
10384 public:
10485
105- prime_sieve (uint64_t n) : Bits(n) // Initialize all to true (potential primes)
86+ prime_sieve (uint64_t n) : Bits(n) // Initialize bits to zero default
10687 {
10788 }
10889
@@ -122,15 +103,21 @@ class prime_sieve
122103
123104 while (factor <= q)
124105 {
125- for (uint64_t num = factor; num < Bits.size (); num += 2 )
106+ // Find the next prime number
107+ for (; factor <= q; factor += 2 )
126108 {
127- if (Bits.get (num ))
109+ if (Bits.get (factor ))
128110 {
129- factor = num;
130111 break ;
131112 }
132113 }
133- Bits.setFlagsFalse (factor * factor, factor + factor);
114+
115+ // Mark multiples of the prime number as not prime
116+ uint64_t start = factor * factor;
117+ for (uint64_t num = start; num <= Bits.size (); num += factor * 2 )
118+ {
119+ Bits.set (num);
120+ }
134121
135122 factor += 2 ;
136123 }
@@ -142,9 +129,9 @@ class prime_sieve
142129
143130 size_t countPrimes () const
144131 {
145- size_t count = (Bits.size () >= 2 ); // Count 2 as prime if within range
146- for (int i = 3 ; i < Bits.size (); i+= 2 )
147- if (Bits.get (i ))
132+ size_t count = (Bits.size () >= 2 ); // Count 2 as prime if within range
133+ for (uint64_t num = 3 ; num <= Bits.size (); num += 2 )
134+ if (Bits.get (num ))
148135 count++;
149136 return count;
150137 }
@@ -155,23 +142,24 @@ class prime_sieve
155142
156143 bool isPrime (uint64_t n) const
157144 {
158- if (n & 1 )
159- return Bits. get (n) ;
160- else
145+ if (n == 2 )
146+ return true ;
147+ if (n < 2 || n % 2 == 0 )
161148 return false ;
149+ return Bits.get (n);
162150 }
163151
164152 // validateResults
165153 //
166- // Checks to see if the number of primes found matches what we should expect. This data isn't used in the
154+ // Checks to see if the number of primes found matches what we should expect. This data isn't used in the
167155 // sieve processing at all, only to sanity check that the results are right when done.
168156
169157 bool validateResults () const
170158 {
171159 const std::map<const uint64_t , const int > resultsDictionary =
172160 {
173- { 10LLU, 4 }, // Historical data for validating our results - the number of primes
174- { 100LLU, 25 }, // to be found under some limit, such as 168 primes under 1000
161+ { 10LLU, 4 }, // Historical data for validating our results - the number of primes
162+ { 100LLU, 25 }, // to be found under some limit, such as 168 primes under 1000
175163 { 1' 000LLU, 168 },
176164 { 10' 000LLU, 1229 },
177165 { 100' 000LLU, 9592 },
@@ -195,8 +183,8 @@ class prime_sieve
195183 if (showResults)
196184 cout << " 2, " ;
197185
198- size_t count = (Bits.size () >= 2 ); // Count 2 as prime if in range
199- for (uint64_t num = 3 ; num <= Bits.size (); num+= 2 )
186+ size_t count = (Bits.size () >= 2 ); // Count 2 as prime if in range
187+ for (uint64_t num = 3 ; num <= Bits.size (); num += 2 )
200188 {
201189 if (Bits.get (num))
202190 {
@@ -215,7 +203,7 @@ class prime_sieve
215203 << " Average: " << duration/passes << " , "
216204 << " Limit: " << Bits.size () << " , "
217205 << " Counts: " << count << " /" << countPrimes () << " , "
218- << " Valid : " << (validateResults () ? " Pass" : " FAIL!" )
206+ << " Valid: " << (validateResults () ? " Pass" : " FAIL!" )
219207 << " \n " ;
220208
221209 // Following 2 lines added by rbergen to conform to drag race output format
@@ -322,7 +310,7 @@ int main(int argc, char **argv)
322310 }
323311
324312 if (bOneshot)
325- cout << " Oneshot is on. A single pass will be used to simulate a 5 second run." << endl;
313+ cout << " Oneshot is on. A single pass will be used to simulate a 5 second run." << endl;
326314
327315 if (bOneshot && (cSecondsRequested > 0 || cThreadsRequested > 1 ))
328316 {
@@ -357,8 +345,8 @@ int main(int argc, char **argv)
357345 else
358346 {
359347 auto tStart = steady_clock::now ();
360- std::thread threads[ cThreads] ;
361- uint64_t l_passes[ cThreads] ;
348+ std::vector<std:: thread> threads ( cThreads) ;
349+ std::vector< uint64_t > l_passes ( cThreads) ;
362350 for (unsigned int i = 0 ; i < cThreads; i++)
363351 threads[i] = std::thread ([i, &l_passes, &tStart](size_t llUpperLimit)
364352 {
0 commit comments