【问题标题】:Long long int makes my Sieve of Eratosthenes super slow?long long int 让我的 Eratosthenes 筛子变得超级慢?
【发布时间】:2017-08-30 06:12:59
【问题描述】:

我有一个程序要求我找到直到10**10-1 (10,000,000,000) 的素数。我写了一个埃拉托色尼筛子来做这个,它工作得非常好(而且准确)高达10**9 (1,000,000,000)。我通过计算它找到的素数来确认它的准确性,它与the chart found here. 上的 50,847,534 的值匹配。我使用unsigned int 作为存储类型,它在大约 30 秒内成功找到了所有素数。

但是,10**10 要求我使用更大的存储类型:long long int。一旦我切换到这个,程序的运行速度就会明显变慢(它已经超过 3 小时而且它仍在工作)。以下是相关代码:

typedef unsigned long long ul_long;
typedef unsigned int u_int;

ul_long max = 10000000000;                            
u_int blocks = 1250000000;
char memField[1250000000];     

char mapBit(char place) {             //convert 0->0x80, 1->0x40, 2->0x20, and so on
    return 0x80 >> (place);
}

for (u_int i = 2; i*i < max; i++) {

    if (memField[i / 8] & activeBit) {               //Use correct memory block
        for (ul_long n = 2 * i; n < max; n += i) {
            char secondaryBit = mapBit(n % 8);       //Determine bit position of n
            u_int activeByte = n / 8;                //Determine correct memory block
            if (n < 8) {                             //Manual override memory block and bit for first block
                secondaryBit = mapBit(n);
                activeByte = 0;
            }
            memField[activeByte] &= ~(secondaryBit);  //Set the flag to false
        }
    }
    activeBit = activeBit >> 1;                       //Check the next
    if (activeBit == 0x00) activeBit = 0x80;
} 

我认为由于10**1010**9 大 10 倍,所以它应该花费 10 倍的时间。这其中的缺陷在哪里?为什么更改为 long long 会导致如此严重的性能问题,我该如何解决?我认识到数字变大了,所以应该会慢一些,但只是到最后。有什么我想念的吗。

注意:我意识到 long int should technically be large enough 但我的 limits.h 说即使我正在编译 64 位也不是。这就是为什么我使用long long int 以防有人想知道。另外,请记住,我没有受过计算机科学培训,只是一个业余爱好者。

编辑:只是在 x86-64 的“发布”中运行它,并建议了一些调试语句。我得到以下输出:

看起来我击中了 u_int 绑定。我不知道为什么i 变得那么大。

【问题讨论】:

  • 您的函数更有可能是 O(n*n) 或更糟。所以如果你从 10 到 9 的幂到 10 的 10 的幂,它会影响持续时间 10 的 (2*10)/10 的 (9*2) 的幂,即 100,而不是10. 或者更糟。
  • 使用&lt;stdint.h&gt; 来避免依赖于平台的整数大小的怪异。
  • 你当然可以做一些优化。只为奇数做一个筛子。并以 n = i*i 开始内部循环,因为 n 的所有较小倍数都已被删除。确保 mapBit 是内联的。并编译为 64 位系统,而不是 32 位。这将产生巨大的影响。
  • 也许我错过了一些东西,但 OP 似乎并没有直接比较两个代码在相同问题大小上的性能。现在所有 OP 可以说的是long long int 版本需要更长的时间来解决一个更大的问题,而不是它比其他版本运行得更慢。
  • @AjayBrahmakshatriya:这个筛子绝对不是 O (n^2)。我认为它是 O (n log log n),实际上是线性的。

标签: c performance sieve-of-eratosthenes sieve


【解决方案1】:

您的程序在for (u_int i = 2; i*i &lt; max; i++) 中有一个无限循环。 iunsigned int,因此 i*i 以 32 位包装,并且始终小于 max。将i 设为ul_long

请注意,对于位 0 到 7,您应该使用从 1 到 0x80 的更简单的位模式。

这是一个完整的版本:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned long long ul_long;
typedef unsigned int u_int;

#define TESTBIT(a, bit)   (a[(bit) / 8] & (1 << ((bit) & 7)))
#define CLEARBIT(a, bit)  (a[(bit) / 8] &= ~(1 << ((bit) & 7)))

ul_long count_primes(ul_long max) {
    size_t blocks = (max + 7) / 8;
    unsigned char *memField = malloc(blocks);
    if (memField == NULL) {
        printf("cannot allocate memory for %llu bytes\n",
               (unsigned long long)blocks);
        return 0;
    }
    memset(memField, 255, blocks);
    CLEARBIT(memField, 0);  // 0 is not prime
    CLEARBIT(memField, 1);  // 1 is not prime
    // clear bits after max
    for (ul_long i = max + 1; i < blocks * 8ULL; i++) {
        CLEARBIT(memField, i);
    }

    for (ul_long i = 2; i * i < max; i++) {
        if (TESTBIT(memField, i)) {           //Check if i is prime
            for (ul_long n = 2 * i; n < max; n += i) {
                CLEARBIT(memField, n);                   //Reset all multiples of i
            }
        }
    }
    unsigned int bitCount[256];
    for (int i = 0; i < 256; i++) {
        bitCount[i] = (((i >> 0) & 1) + ((i >> 1) & 1) +
                       ((i >> 2) & 1) + ((i >> 3) & 1) +
                       ((i >> 4) & 1) + ((i >> 5) & 1) +
                       ((i >> 6) & 1) + ((i >> 7) & 1));
    }
    ul_long count = 0;
    for (size_t i = 0; i < blocks; i++) {
        count += bitCount[memField[i]];
    }
    printf("count of primes up to %llu: %llu\n", max, count);
    free(memField);
    return count;
}

int main(int argc, char *argv[]) {
    if (argc > 1) {
        for (int i = 1; i < argc; i++) {
            count_primes(strtoull(argv[i], NULL, 0));
        }
    } else {
        count_primes(10000000000);
    }
    return 0;
}

10^9 需要 10 秒,10^10 需要 131 秒:

count of primes up to 1000000000: 50847534
count of primes up to 10000000000: 455052511

【讨论】:

  • 对于2 * i 表达式,可能需要在内循环中发生类似的事情。
  • @MichaelBurr:好点!将i 设为ul_long 是更安全的解决方案。
  • 嗯......算法有些可疑。尝试最大值为541(其中有100 素数),但输出报告103?然后尝试最大522 它现在报告104 素数...但它确实适用于偶数1001000 等数字。 (奔腾数学?)
  • @DavidC.Rankin 发生这种情况是因为memField 有多个max 位,如果max 不是8 的倍数并且剩余位设置为1 并计数结果。
  • memset之后添加for (ul_long i = max + 1; i &lt; blocks * 8; ++i) CLEARBIT(memField, i);可以解决这个问题:ideone.com/K1OGX9
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 2019-12-12
  • 2016-04-08
相关资源
最近更新 更多