Last active
February 12, 2024 17:13
-
-
Save Lynxaa/a9f6cb18b9a2550ff0a91308142d97d2 to your computer and use it in GitHub Desktop.
A quick example of how you can improve the space efficiency of Sieve of Eratosthenes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> // std::cout, std::endl | |
#include <cstring> // std::memset | |
#include <cstddef> | |
#include <memory> | |
// This code isn't good, just written quickly as a PoC, uses c-style casts and such, etc etc, avoid using this in it's current state. | |
// This is heavily untested and wrote for the purpose of this example. | |
// In this context, a "block" references to a region of bits, this could be 8, 16, 32, 64 bits, whatever datatype you want. | |
class DynamicBitset { | |
private: | |
static const size_t BITS_PER_BLOCK = 32; | |
static size_t block_index( const size_t pos ) { | |
return pos / BITS_PER_BLOCK; | |
} | |
static size_t bit_index( size_t pos ) { | |
return ( size_t )( pos % BITS_PER_BLOCK ); | |
} | |
// the datatype used here should be the same bit width as the "block" datatype, i.e., in this example, 32 bits. | |
static int bit_mask( size_t pos ) { | |
return ( int )( 1 << bit_index( pos ) ); | |
} | |
// You'd also want to store more information here so you can handle things like realloc and so on, and probably use a vector? | |
std::unique_ptr< int[] > m_buffer; | |
int m_num_bits; | |
size_t m_num_blocks; | |
public: | |
DynamicBitset( const int num_bits ) { | |
m_num_bits = num_bits; | |
// How many bytes do we need to store N bits | |
m_num_blocks = ( m_num_bits / BITS_PER_BLOCK ) + 1; | |
m_buffer = std::make_unique< int[] >( m_num_blocks ); | |
memset( ( void* )( m_buffer.get() ), 0xFFFFFFFF, sizeof( int ) * m_num_blocks ); // set all bits to 1 for the example | |
} | |
// Set the bit at the position to 0 | |
void reset( const size_t pos ) { | |
m_buffer[ block_index( pos ) ] &= ~bit_mask( pos ); | |
} | |
// Set a bit at the given position | |
void set( const size_t pos, const bool value ) { | |
if( value ) { | |
m_buffer[ block_index( pos ) ] |= bit_mask( pos ); | |
return; | |
} | |
reset( pos ); | |
} | |
// Test if a bit is set | |
bool test( const size_t pos ) { | |
return ( m_buffer[ block_index( pos ) ] & bit_mask( pos ) ) != 0; | |
} | |
// Just a debug thing to indicate how many bytes of memory are in use. | |
const size_t num_blocks() { | |
return m_num_blocks; | |
} | |
}; | |
// https://www.geeksforgeeks.org/sieve-of-eratosthenes/ | |
void SieveOfEratosthenes(int n) | |
{ | |
// Create a boolean array "prime[0..n]" and initialize | |
// all entries it as true. A value in prime[i] will | |
// finally be false if i is Not a prime, else true. | |
bool prime[n + 1]; | |
std::memset( &prime[ 0 ], true, sizeof( prime ) ); | |
for (int p = 2; p * p <= n; p++) { | |
// If prime[p] is not changed, then it is a prime | |
if (prime[p] == true) { | |
// Update all multiples of p greater than or | |
// equal to the square of it numbers which are | |
// multiple of p and are less than p^2 are | |
// already been marked. | |
for (int i = p * p; i <= n; i += p) | |
prime[i] = false; | |
} | |
} | |
// Print all prime numbers | |
for (int p = 2; p <= n; p++) | |
if (prime[p]) | |
std::cout << p << " "; | |
// Print out memory usage stats | |
std::cout << "\n\tmemory usage: " << sizeof( bool ) * ( n + 1 ) << " bytes..\n"; | |
std::cout << "\tmemory usage: " << ( float )( sizeof( bool ) * ( n + 1 ) ) / 1024 << " KB..\n"; | |
std::cout << "\tmemory usage: " << ( float )( sizeof( bool ) * ( n + 1 ) ) / 1024 / 1024 << " MB..\n"; | |
} | |
// SieveOfEratosthenes implementation using a dynamic bitset implementation instead of a boolean array. | |
void SieveOfEratosthenesBitset( const int n ) { | |
DynamicBitset bitset( n ); | |
for( int p = 2; p * p <= n; p++ ) { | |
if( bitset.test( p ) ) { | |
for( int i = p * p; i <= n; i += p ) { | |
bitset.set( i, 0 ); | |
} | |
} | |
} | |
// Print all prime numbers | |
for (int p = 2; p <= n; p++) | |
if( bitset.test( p ) ) | |
std::cout << p << " "; | |
// Print out memory usage stats | |
std::cout << "\n\tmemory usage: " << sizeof( int ) * bitset.num_blocks() << " bytes..\n"; | |
std::cout << "\tmemory usage: " << ( float )( sizeof( int ) * bitset.num_blocks() ) / 1024 << " KB..\n"; | |
std::cout << "\tmemory usage: " << ( float )( sizeof( int ) * bitset.num_blocks() ) / 1024 / 1024 << " MB..\n"; | |
} | |
// Driver Code | |
int main() | |
{ | |
int n = 10000; | |
std::cout << "Following are the prime numbers smaller " | |
<< " than or equal to " << n << std::endl; | |
SieveOfEratosthenes(n); | |
std::cout << "\n"; | |
SieveOfEratosthenesBitset( n ); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output: