Skip to content

Instantly share code, notes, and snippets.

@Lynxaa
Last active February 12, 2024 17:13
Show Gist options
  • Save Lynxaa/a9f6cb18b9a2550ff0a91308142d97d2 to your computer and use it in GitHub Desktop.
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
#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;
}
@Lynxaa
Copy link
Author

Lynxaa commented Feb 12, 2024

Example output:

Following are the prime numbers smaller  than or equal to 10000
2 3 5 7 11 13 17 19 23 29 31 37 41 ....
	memory usage: 10001 bytes..
	memory usage: 9.7666 KB..
	memory usage: 0.0095377 MB..

2 3 5 7 11 13 17 19 23 29 31 37 41 ....
	memory usage: 1252 bytes..
	memory usage: 1.22266 KB..
	memory usage: 0.001194 MB..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment