4

当我似乎有足够的(虚拟?)可用内存时,我试图理解为什么会出现 std::bad_alloc 异常。本质上,我有一个素数生成器(Eratosthenes 筛子(尚未分段)),我在其中为指标数组更新布尔值,然后为我在命令行上指定的界限下找到的素数更新整数。

我有 1GB RAM(其中一些将被我的操作系统占用(ubuntu 10.04),并且可能其中一些不能用作堆内存(我错了吗?))和 2.8 GB 的交换空间(我相信这是自动的在安装 Ubuntu 时为我设置)

如果我将上限设置为 600000000,那么我要求为我的指标数组提供 0.6 GB 内存和大约 30000000*4 字节(略微高估,因为有 26355867 个小于 500000000 的素数)用于我的素数数组和一些变量这里和那里; 这意味着我要求大约 0.72(+ 可忽略不计)GB 的内存,我认为它应该被我可用的交换空间覆盖(我知道触摸这些东西会荒谬地减慢我的程序速度)。但是我得到了 std::bad_allocs。

谁能指出我在这里缺少什么?(在粘贴我的最后一个错误之前将 long long ints 更改为 ints 的最后一件事是段错误(我的数字远低于 2^31,所以我看不到我溢出的地方) - 仍然试图弄清楚那个)

我的代码如下(并且不会剥夺我自己对更快算法等进行调查的好处。我会很感激这里的任何代码改进!(即,如果我犯了主要的禁忌))

主文件

#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;
}

在我阅读的同时,有人对此有任何见解吗?

编辑由于某种原因,我决定开始使用 lastStorageSize ints 而不是 noOfPrime 来更新我的素数列表!感谢 David Fischer 发现了那个!

我现在可以超过 600000000 作为上限

4

4 回答 4

4

您可以在程序中使用的内存量受以下两者中较小者的限制:1) 可用的虚拟内存,2) 可用的地址空间

如果您在具有平面内存模型的平台上将程序编译为 32 位可执行文件,则单个进程的可寻址空间的绝对限制为 4GB。在这种情况下,您有多少可用交换空间完全无关紧要。即使您仍有大量空闲交换空间,您也不能在平面内存 32 位程序中分配超过 4GB 的空间。此外,这 4GB 可用地址中的很大一部分将保留用于系统需求。

在这样的 32 位平台上分配大量交换空间确实有意义,因为它可以让您一次运行多个进程。但它并没有克服每个特定进程的 4GB 地址空间障碍。

基本上,将其视为电话号码可用性问题:如果某个地区使用 7 位电话号码,那么一旦您用完该地区可用的 7 位电话号码,为该地区制造更多电话不再有意义- 它们将无法使用。通过添加交换空间,您实际上是“制造手机”。但是您已经用完了可用的“电话号码”。

当然,对于平面内存模型 64 位平台,同样的问题也正式存在。但是,64位平台的地址空间太大了,已经不是瓶颈了(你知道,“64位应该对每个人都足够了”:))

于 2012-11-26T01:06:31.867 回答
1

既然程序的内存使用情况很容易分析,那就让内存布局完全固定好了。不要动态分配任何东西。用于std::bitset获取固定大小的位向量,并将其设为全局变量。

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

这不会占用磁盘空间。当您沿着阵列前进时,操作系统只会分配零页。它可以更好地利用每一位。

当然,一半的位代表偶数,尽管只有一个偶素数。这里有几个主要的生成器可以优化这些东西。

顺便说一句,new如果可能,最好避免显式编写,避免从构造函数中调用函数,并重新抛出std::bad_alloc以避免让对象被构造成无效状态。

于 2012-11-26T01:21:34.507 回答
1

当你分配筛子时,

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

您设置lastStorageSizenum,素数的给定界限。然后你永远不会改变它,并且

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

尝试分配一个元素int数组。lastStorageSize

如果num大约是 5 亿,那么您请求的大约是 2 GB。根据操作系统/过度使用策略,bad_alloc即使您实际上只需要一小部分空间,这也很容易导致。

筛分完成后,您设置noOfPrimes找到的素数的计数 - 使用该数字来分配素数数组。

于 2012-11-26T01:28:43.793 回答
1

第一个问题是“还有哪些其他进程正在运行?” 所有正在运行的进程共享 2.87 GB 的交换空间;它不是每个进程。坦率地说,在现代系统上,2.8 GB 对我来说听起来相当低。我不会尝试运行内存小于 2GB 和交换空间小于 4GB 的最新版本的 Windows 或 Linux。(最近的 Linux 版本,尤其是在 Ubuntu 发行版中,似乎启动了很多占用内存的守护进程。)您可能想尝试top按虚拟内存大小排序,看看其他进程占用了多少内存.

cat /proc/meminfo还将为您提供有关实际使用的内容的许多有价值的信息。(在我的系统上,只运行了几个xtermwith bash,加上Firefox,我只有 3623776 kB 可用,在一个 8GB 的​​系统上。一些被计为使用的内存可能是磁盘缓存之类的东西,如果应用程序系统可以缩减请求内存。)

其次,关于您的段错误:默认情况下,Linux 并不总是报告 allways 正确报告分配失败;它经常会撒谎,告诉你你有记忆,而你没有。试试cat /proc/sys/vm/overcommit_memory。如果它显示为零,那么您需要更改它。如果是这种情况,请尝试echo 2 > /proc/sys/vm/overcommit_memory(并在其中一个 rc 文件中执行此操作)。您可能还必须更改 /proc/sys/vm/overcommit_ratio以获得可靠的行为sbrk(两者都malloc依赖operator new)。

于 2012-11-26T01:31:05.257 回答