【问题标题】:Generate primes from 1 to n, crashing for n > 300 million生成从 1 到 n 的素数,n > 3 亿时崩溃
【发布时间】:2014-11-30 10:05:10
【问题描述】:

关于我如何让这个程序为 n = 1 万亿(除了进行升级/购买新计算机)工作有什么建议吗?

错误如下:构建后,正在执行的程序(弹出命令行样式的输出窗口)然后快速关闭,出现如下错误“ProjectPrimes.exe已停止工作(Windows正在寻找这个问题的解决方案。”我怀疑这与内存问题有关,因为我第一次遇到它是在 n = 2000 万,但那是在我选择 malloc/free 'sieve' 数组之前(即,我的 'sieve' 数组是维度为 nx 1 且每个元素由 1 或 0 组成的大数组。

该程序需要大约 35 秒来运行前 3 亿个整数(16,252,325 个素数),所以没关系,但没什么了不起的。正如我所提到的,目标是能够生成低于 1 万亿的素数,所以还有很长的路要走......

如果相关,这是我的机器规格(以防目标在这台机器上不合理):2.40ghz i5、4gb RAM、64 位 Windows 7。

对于不熟悉的人的方法概述:我们使用 Sundaram 筛法。在不进入证明的情况下,我们首先使用筛函数消除整数“n”以下的所有奇数非素数:[2*(i+j+2*i*j)+1 | i

为什么素数函数会返回(指向包含数组的指针)n 以下的完整素数集?好吧,目标是能够识别(i)n以下的素数以及(ii)列出n以下的素数。这也是为什么我选择传递一个指向 n 以下素数计数的指针作为参数。

这是不那么令人兴奋的“主要”功能:

int main() {
    long ceiling = 300*1000*1000;
    long *numPrimes;
    long *primes;

    primes = primesToSS(ceiling+1, numPrimes);
    printf("\n\nThere are %d primes below %d.\n\n",*numPrimes,ceiling);

    free(primes);
    return 0;
}

这是肉:

//n represents the ceiling, i.e., the integer below which we will generate primes
//cnt* is a pointer which will point the number of primes below n

long* primesToSS( long n, long* cnt ) {

    //initialize sieve by setting all elements equal to 1 (except for 0 and 1)
    long *sieve = malloc(n*sizeof(long));
    initArray(sieve, n, 1);
    sieve[0] = 0; sieve[1] = 0;

    //eliminate all odd composite numbers
    for (int i = 1; i <= n/2; ++i)
        for (int j = i; j <= (n-2*i)/(2*(2*i+1)); ++j)
            sieve[ 2*(i+j+2*i*j)+1 ] = 0;

    //eliminate all even numbers greater than two 
    //and count total number of primes below n
    long numPrimes = 1;
    for (int i = 3; i < n; ++i) {
        if (i % 2 == 0) sieve[i] = 0;
        numPrimes += sieve[i];
    }
    *cnt = numPrimes;

    //create array of primes
    long *primes = malloc(numPrimes*sizeof(int));
    long counter = 0;
    for (int i = 0; i < n; ++i) {
        if (sieve[i] == 1) {
            primes[counter] = i;
            counter++; } 
    }
    free(sieve);
    return primes;
}

void initArray( int* arr, int len, int n ) {
    for( int i = 0; i < len; ++i) arr[i] = n; }

【问题讨论】:

  • 当你的代码崩溃时会出现什么样的错误/异常?
  • 请尝试提供一个最小的、可编译的、仍能显示问题的工作示例。除此之外,显然 malloc(n*sizeof(long)) 对于足够大的 n 将返回 NULL。
  • 你的 sieve 数组不是标志数组吗,即零或一?为什么要使用 long 来存储单个位?
  • 指针numPrimes 在传递给primesToSS() 时未初始化。在该函数内部,*cnt = numPrimes; 行将写入随机内存位置。
  • 您应该在格式中使用%ld 来打印long 值。使用 64 位 long 值来存储 0 或 1 值似乎也有点过分。如果您使用单个位,您将能够在筛子中存储更多值。你也可以考虑根本不用存储任何偶数;位数组可以简单地对奇数进行编码(但它允许您将 128 个数字放入当前仅获得 1 的数组的每个元素中)。

标签: c primes sieve-of-eratosthenes


【解决方案1】:
long *numPrimes;
...
primes = primesToSS(ceiling+1, numPrimes);
printf("\n\nThere are %d primes below %d.\n\n",*numPrimes,ceiling);

正如 Blastfurnace 所指出的,您正在取消引用(并通过存储)未初始化的指针。这应该是

long numPrimes;
...
primes = primesToSS(ceiling+1, &numPrimes);
printf("\n\nThere are %d primes below %d.\n\n", numPrimes, ceiling);

很可能还有其他错误……尤其是没有检查 malloc 失败。哦,这是一个:

long *sieve;
initArray(sieve, n, 1);

...

void initArray( int* arr, int len, int n ) {
    for( int i = 0; i < len; ++i) arr[i] = n; }

intlong 是不同的类型。在这里你再做一次:

long *primes = malloc(numPrimes*sizeof(int));

最佳实践是使用 sizeof 元素,而不是 type,这样可以避免这种事情:

long *primes = malloc(numPrimes * sizeof *primes);

检查您的代码是否有其他明显的错误。

样式说明:在行尾放置 } 是一种不好的做法,并且在您稍后添加另一行时乞求一个错误。

【讨论】:

  • 哇,一个反社会的驱车投反对票……我在同一分钟内得到了两个。
【解决方案2】:

一些内存注意事项:对于前 1 万亿个素数,如果您用一位表示每个数字,则需要 1 万亿/8 个字节。那就是 125000000000 = 119209 MB = 116 GB 的 RAM。

虽然您可以要求您的操作系统创建一个 200GB 的 SWAP 分区,但性能可能会很差。如果你想保持相同的算法,那么你需要看memory mapped files

这仍然会很慢,但至少你可以分配一个足够大的数组。

下一步将是查看不同的主要算法,或者您需要研究内存压缩算法。在您的情况下,sparse matrices 将无法正常工作,因为您的起始矩阵全为 1,并且您最终删除了几乎所有元素。

【讨论】:

  • OP 的程序崩溃了,这没有解决。虽然这是对第一句话中问题的回答,但这似乎并不是 OP 的实际重点。附:阿特金筛法是解决内存问题的更好算法。
  • @JimBalter:其他答案已经解释了这些错误。我试图回答 OP 应该注意的更普遍的问题。
【解决方案3】:

让我们做一些观察:

  • 正如人们在 cmets 和答案中提到的那样,您只需要 1 位来存储数字是否为素数。因此,您可以将 8 个数字的数据打包成 1 个字节。实现自己的 bitset 数据结构,让代码更简洁。
  • 在cmets中也提到过,由于所有大于2的素数都是奇数,所以偶数不需要存储数据。这允许您将 16 个数字打包成 1 个字节。
  • 遵循wheel factorization 的想法,如果您只存储与 1 或 5 模 6 一致的数字的位,则可以进一步将 24 个数字打包成 1 个字节。更高的模数将节省更多空间(收益递减),但访问 bitset 数据结构的逻辑可能会减慢算法速度。
  • 要让程序处理 1 万亿 (1012) 个数,在您过筛时,您只需收集小于 100 万个 (106) 的素数列表支持>)。更大的数字是不必要的,因为小于 1 万亿的复合数字中的其他因素已经包含在列表中。
  • 以 16 个数字/字节打包数字(您应该进行的最基本设置),您将需要约 58 GiB 的 RAM。但是,没有必要为整个范围分配。只需分配几亿到几十亿的较小范围的数字(尝试改变数字以获得最高性能),最多转换为几百 MiB,然后使用小于 100 万的素数列表进行筛选范围。

    这一步可以并行完成——你只需要分享小于100万的素数列表。

    顺便说一下,这种技术称为分段筛。

【讨论】:

  • 想解释一下第四步吗?据我了解问题中的代码,它不会尝试找到所有素数 factors
  • @AaronDigulla:是的。如果你实现一个高效的筛子,你将从 p^2 开始标记,因为 1, 2, ..., (p-1) in p*1, p*2, ..., p*(p-1 ) 在早先的小于 p 的素数筛选中已经涵盖。因此,我们将在这里执行 2 个步骤: 1) 找到 sqrt(max) 的所有素数 2) 进行筛分(筛分后,只需遍历 bitset 以获得素数)。第 2 步在第 5 条中说明。
  • 所以这甚至不能解释为什么代码会崩溃,但它被接受为答案?多么奇怪。
  • @JimBalter:我回答关于如何找到 1 万亿以下素数的问题,这是 OP 的最终目标。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 2021-12-14
  • 1970-01-01
相关资源
最近更新 更多