HexedAgain
HexedAgain

Reputation: 1031

std::bad_alloc without going into swap space

I'm trying to understand why I am getting std::bad_alloc exceptions when I seem to have enough (virtual?) memory available to me. Essentially I have a prime number generator (Eratosthenes sieve (not segmented yet)) where I'm newing bools for an indicator array, and then newing ints for the primes I've found under a bound I specify on the command line.

I have 1GB RAM (some of this will be hogged by my OS (ubuntu 10.04), and probably some of it is not available as heap memory (am I wrong here?)) and 2.8 GB of swap space (I believe that was auto set for me when installing Ubuntu)

If I set an upper bound of 600000000 then I'm asking for 0.6 GB of memory for my indicator array and roughly 30000000*4 bytes (slight over estimate given there are 26355867 primes less than 500000000) for my primes array, and a few variables here and there; this means I'm asking for about .72 (+ negligible) GB of memory which I believe should be covered by the swap space available to me (I am aware touching that stuff will slow my program down ridiculously). However I am getting std::bad_allocs.

Could anyone point out what I'm missing here? (one last thing having changed long long ints to ints before pasting my last error was a seg fault (my numbers are way below 2^31 though so I can't see where I'm overflowing) - still trying to figure that one out)

My code is as follows (and without taking away from me the benefit of my own investigation into quicker algorithms etc.. I'd be appreciative of any code improvements here! (i.e. if I'm committing major no-no s))

main.cpp

#include <iostream>
#include <cmath>
#include "Prime.hpp"
#include <ctime>
#include <stdio.h>
#include <cstring>

//USAGE: execute program with the nth prime you want and an upper bound for finding primes --too high may cause bad alloc
int main(int argc, const char *argv[])
{
    int a = strlen(argv[1]);
    clock_t start = clock();
    if(argc != 2)
    {
        std::cout << "USAGE: Enter a positive inputNumber <= 500000000.\n"
          << "This inputNumber is an upper bound for the primes that can be found\n";
        return -1;
    }
    const char* primeBound = argv[1];
    int inputNum = 0;
    for(int i = 0; i < strlen(argv[1]); i++)
    {
    if(primeBound[i] < 48 || primeBound[i] > 57 || primeBound[0] == 48)
    {
        std::cout << "USAGE: Enter a positive inputNumber <= 500000000.\n"
              << "This inputNumber is an upper bound for the primes that can be found\n";
        return -1;
    }
    inputNum = (int)(primeBound[i]-48) + (10 * inputNum);
    }
    if(inputNum > 600000000)//getting close to the memory limit for this machine (1GB - memory used by the OS):
                   //(each bool takes 1 byte and I'd be asking for more than 500 million of these 
                   //and I'd also asking for over 100000000 bytes to store the primes > 0.6 GB)
    {
    std::cout << "USAGE: Enter a positive inputNumber <= 500000000.\n"
          << "This inputNumber is an upper bound for the primes that can be found\n";
        return -1;
    }

    Prime p(inputNum);
    std::cout << "the largest prime less than " << inputNum << " is: " << p.getPrime(p.getNoOfPrimes()) << "\n";
    std::cout << "Number of primes: " << p.getNoOfPrimes() << "\n";
    std::cout << ((double)clock() - start) / CLOCKS_PER_SEC << "\n";
  return 0;
}

Prime.hpp

#ifndef PRIME_HPP
#define PRIME_HPP
#include <iostream>
#include <cmath>

class Prime
{
    int lastStorageSize;
    bool* primeIndicators;
    int* primes;
    int noOfPrimes;

    void allocateIndicatorArray(int num);
    void allocatePrimesArray();
    void generateIndicators();
    void generatePrimeList();
    Prime(){}; //forcing constructor with param = size

    public:
        Prime(int num);
    int getNoOfPrimes();
    int getPrime(int nthPrime);
    ~Prime(){delete [] primeIndicators; delete [] primes;}
};

#endif

Prime.cpp

#include "Prime.hpp"
#include <iostream>

//don't know how much memory I will need so allocate on the heap
void Prime::allocateIndicatorArray(int num)
{
    try
    {
        primeIndicators = new bool[num];
    }
    catch(std::bad_alloc ba)
    {
    std::cout << "not enough memory :[";
    //if I'm looking for a particular prime I might have over-allocated here anyway...might be worth
    //decreasing num and trying again - if this is possible!
    }
    lastStorageSize = num;
}

void Prime::allocatePrimesArray()
{
    //could probably speed up generateIndicators() if, using some prime number theory, I slightly over allocate here
    //since that would cut down the operations dramatically (a small procedure done many times made smaller)
    try
    {
        primes = new int[lastStorageSize];
    }
    catch(std::bad_alloc ba)
    {
    std::cout << "not enough memory :[";
    //if I'm looking for a particular prime I might have over-allocated here anyway...might be worth
    //decreasing num and trying again - if this is possible!
    }
}
void Prime::generateIndicators()
{
    //first identify the primes -- if we see a 0 then start flipping all elements that are multiples of i starting from i*i (these will not be prime)
    int numPrimes = lastStorageSize - 2; //we'll be starting at i = 2 (so numPrimes is at least 2 less than lastStorageSize)
    for(int i=4; i < lastStorageSize; i+=2)
    {
        primeIndicators[i]++; //dispense with all the even numbers (barring 2 - that one's prime)
    numPrimes--;
    }
    //TODO here I'm multiple counting the same things...not cool >;[
    //may cost too much to avoid this wastage unfortunately
    for(int i=3; i < sqrt(double(lastStorageSize)); i+=2) //we start j at i*i hence the square root
    {
        if(primeIndicators[i] == 0)
    {
            for(int j = i*i; j < lastStorageSize; j = j+(2*i)) //note: i is prime, and we'll have already sieved any j < i*i
        {
        if(primeIndicators[j] == 0)
        {
            numPrimes--;//we are not checking each element uniquely yet :/
                    primeIndicators[j]=1;
        }
        }
    }
    }
    noOfPrimes = numPrimes;
}
void Prime::generatePrimeList()
{
    //now we go and get the primes, i.e. wherever we see zero in primeIndicators[] then populate primes with the value of i
    int primesCount = 0;
    for(int i=2;i<lastStorageSize; i++)
    {
        if(primeIndicators[i] == 0)
        {
        if(i%1000000 = 0)
        std::cout << i << " ";
            primes[primesCount]=i;
            primesCount++;
        }
    }
}
Prime::Prime(int num)
{
    noOfPrimes = 0;
    allocateIndicatorArray(num);
    generateIndicators();
    allocatePrimesArray();
    generatePrimeList();
}

int Prime::getPrime(int nthPrime)
{
    if(nthPrime < lastStorageSize)
    {
        return primes[nthPrime-1];
    }
    else
    {
    std::cout << "insufficient primes found\n";
    return -1;
    }
}

int Prime::getNoOfPrimes()
{
    return noOfPrimes;
}

Whilst I'm reading around has anybody got any insight on this?

edit For some reason I decided to start newing my primes list with lastStorageSize ints instead of noOfPrime! thanks to David Fischer for spotting that one!

I can now exceed 600000000 as an upper bound

Upvotes: 4

Views: 2127

Answers (4)

AnT stands with Russia
AnT stands with Russia

Reputation: 320631

The amount of memory you can use inside your program is limited by the lesser of the two: 1) the available virtual memory, 2) the available address space.

If you are compiling your program as a 32-bit executable on a platform with flat memory model, the absolute limit of addressable space for a single process is 4GB. In this situation it is completely irrelevant how much swap space you have available. You simply can't allocate more than 4GB in a flat-memory 32-bit program, even if you still have lots of free swap space. Moreover, a large chunk of those 4GB of available addresses will be reserved for system needs.

On such a 32-bit platform allocating a large amount of swap space does make sense, since it will let you run multiple processes at once. But it does nothing to overcome the 4GB address space barrier for each specific process.

Basically, think of it as a phone number availability problem: if some region uses 7-digit phone numbers, then once you run out of the available 7-digit phone numbers in that region, manufacturing more phones for that region no longer makes any sense - they won't be usable. By adding swap space you essentially "manufacturing phones". But you have already run out of available "phone numbers".

The same issue formally exists, of course, with flat-memory model 64-bit platforms. However, the address space of 64-bit platform is so huge, that it is no longer a bottleneck (you know, "64-bit should be enough for everyone" :) )

Upvotes: 4

James Kanze
James Kanze

Reputation: 153977

The first question is "what other processes are running?" The 2.87 GB of swap space is shared between all of the running processes; it is not per process. And frankly, on a modern system, 2.8 GB sounds fairly low to me. I wouldn't try to run recent versions of Windows or Linux with less than 2GB ram and 4GB swap. (Recent versions of Linux, at least in the Ubuntu distribution, especially, seem to start up a lot of daemons which hog the memory.) You might want to try top, sorted on virtual memory size, just to see how much other processes are taking.

cat /proc/meminfo will also give you a lot of valuable information about what is actually being used. (On my system, running just a couple of xterm with bash, plus Firefox, I have only 3623776 kB free, on a system with 8GB. Some of the memory counted as used is probably things like disk caching, which the system can scale back if an application requests memory.)

Second, concerning your seg faults: by default, Linux doesn't always report allways report allocation failures correctly; it will often lie, telling you that you have the memory, when you don't. Try cat /proc/sys/vm/overcommit_memory. If it displays zero, then you need to change it. If this is the case, try echo 2 > /proc/sys/vm/overcommit_memory (and do this in one of the rc files). You may have to change the /proc/sys/vm/overcommit_ratio as well to get reliable behavior from sbrk (which both malloc and operator new depend on).

Upvotes: 1

Daniel Fischer
Daniel Fischer

Reputation: 183968

When you allocate the sieve,

void Prime::allocateIndicatorArray(int num)
{
    try
    {
        primeIndicators = new bool[num];
    }
    catch(std::bad_alloc ba)
    {
    std::cout << "not enough memory :[";
    }
    lastStorageSize = num;
}

you set lastStorageSize to num, the given bound for the primes. Then you never change it, and

void Prime::allocatePrimesArray()
{
    try
    {
        primes = new int[lastStorageSize];
    }
    catch(std::bad_alloc ba)
    {
    std::cout << "not enough memory :[";
    }
}

try to allocate an int array of lastStorageSize elements.

If num is around 500 million, that's around 2 GB that you request. Depending on operating system/overcommitting strategy, that can easily cause a bad_alloc even though you only need a fraction of the space actually.

After the sieving is finished, you set noOfPrimes to the count of found primes - use that number to allocate the primes array.

Upvotes: 1

Potatoswatter
Potatoswatter

Reputation: 137870

Since the memory usage of the program is so easy to analyze, just let the memory layout be completely fixed. Don't dynamically allocate anything. Use std::bitset to get a fixed-size bitvector, and make that a global variable.

std::bitset< 600000000 > indicators; // 75 MB

This won't take up space on disk. The OS will just allocate pages of zeroes as you progress along the array. And it makes better use of each bit.

Of course, half the bits represent even numbers, despite there being only one even prime. Here are a couple prime generators that optimize out such things.

By the way, it's better to avoid explicitly writing new if possible, avoid calling functions from the constructor, and to rethrow the std::bad_alloc to avoid allowing the object to be constructed into an invalid state.

Upvotes: 1

Related Questions