【问题标题】:Calculating primes using only odd divisors is slower仅使用奇数除数计算素数较慢
【发布时间】:2019-06-07 02:35:46
【问题描述】:

我编写了一个小 C 程序来计算素数,现在尝试尽可能优化代码。

在程序的第一个版本中,我检查一个数字是否为偶数(模 2),如果是,我将继续下一个数字。

在我的第二次修订中,我尝试通过将要检查的数字增加 2 来仅检查奇数是否可能是素数(所以我将从 3 开始,然后检查 5,然后检查 7,然后检查 9,然后检查 11 等) .

我认为这会更快,因为我已经用我的代码对模 2 进行了额外检查,并简单地将其替换为加法。 然而,令我惊讶的是,仅检查奇数的代码在大多数情况下运行速度比检查所有数字的实现慢一点。

这是代码(它包含我在 cmets 中的修订之间所做的更改,无论它说的是//CHANGE)

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

unsigned long i = 3; //CHANGE No 1 - it was i = 2;

bool hasDivisor(unsigned long number)
{
    //https://stackoverflow.com/questions/5811151/why-do-we-check-up-to-the-square-root-of-a-prime-number-to-determine-if-it-is-pr
    unsigned long squareRoot = floor(sqrt(number));

    //we don't check for even divisors - we want to check only odd divisors
    //CHANGE No 2 - it was (number%2 ==0)
    if(!((squareRoot&1)==0)) //thought this would boost the speed
    {  
        squareRoot += 1;
    }

    for(unsigned long j=3; j <= squareRoot; j+=2)
    {
        //printf("checking number %ld with %ld \n", number, j);
        if(number%j==0)
        {
            return true;
        }
    }
    return false;
}

int main(int argc, char** argv)
{
    printf("Number 2 is a prime!\n");
    printf("Number 3 is a prime!\n");

    while(true)
    {
        //even numbers can't be primes as they are a multiple of 2
        //so we start with 3 which is odd and contiously add 2
        //that we always check an odd number for primality
        i++; //thought this would boost the speed instead of i +=2;
        i++; //CHANGE No 3 - it was a simple i++ so eg 3 would become 4 and we needed an extra if(i%2==0) here

        //search all odd numbers between 3 and the (odd ceiling) sqrt of our number
        //if there is perfect division somewhere it's not a prime
        if(hasDivisor(i))
        {
            continue;
        }        
        printf("Number %ld is a prime!\n", i);
    }
    return 0;
}

我正在使用 Arch Linux x64 和 GCC 版本 8.2.1 并编译:

gcc main.c -lm -O3 -o primesearcher

虽然我也用 O1 和 O2 进行了测试。

我正在使用以下命令进行“基准测试”:

./primesearcher & sleep 10; kill $! 

它运行程序 10 秒,并在这段时间内向终端输出素数,然后终止它。 我显然尝试让程序运行更多(30、60 和 180 秒),但结果大约有 9/10 的时间有利于版本检查偶数(模 2 版本在被杀死之前发现了更大的素数) .

知道为什么会这样吗? 最后可能代码有问题?

【问题讨论】:

  • 代码运行正常,那么这个问题更适合 CodeReview 吗?
  • 数字 1 不是素数
  • 啊,请不要通过更改您发布的代码让 cmets 看起来很愚蠢。我不明白你为什么要搞乱平方根。你可以有unsigned long squareRoot = round(sqrt(number));squareRoot 是奇数还是偶数都没有关系。
  • 你是否完全达到限制并不重要。仅当您大于限制时才需要停止。无论您的最后一次检查是 limit-1 还是 limit 都没有任何区别。反正你只能打其中一个。
  • 要回答为什么一个版本的程序运行速度比另一个版本快,我们需要查看两个版本。一方面,您没有展示您声称速度更快的程序版本,另一方面,您提供了您声称速度较慢的程序的 多个 版本。这不行。由于您似乎广泛关注优化而不是某个特定更改,因此我认为 Code Review 会比当前问题更好地为您服务,而不是像 WeatherVane 已经建议的那样。

标签: c optimization primes mathematical-optimization


【解决方案1】:

代码比没有测试的代码要慢if(!((squareRoot&amp;1)==0)),因为它很少有好处。

请记住,对于大多数 number,由于从 number%j 测试提前返回,迭代限制永远不会达到。随着number 的增长,素数往往会变得稀有。

一次罕见的额外迭代不会被测试的重复成本所抵消。

比较!((squareRoot&amp;1)==0)number%2 ==0moot

当差异很小时,OP 的测试速度方法容易出错:“大多数时候运行有点慢”表明不一致。

大量时间在printf() 中。为了比较主要的计算性能,需要消除 I/O。

kill 也不是那么精确。

相反,当i 达到类似 4,000,000,000 的值并计时,形成一个循环停止。


代码还有其他弱点:

unsigned long squareRoot = floor(sqrt(number)); 可以为大的number 创建错误的根,因为在将number 转换为doublesqrt() 例程不精确时会进行舍入。 OP 的reference 解决了数学算法 的需求。然而,考虑到实际计算的局限性,这种 C 代码实现很容易失败。

建议

// Prime test for all unsigned long number
bool isPrime(unsigned long number) {
  if (number % 2 == 0) {  // This can be eliminated if `number` is always odd.
    return number == 2;
  }

  for (unsigned long j = 3; j <= number/j; j += 2) {
    if (number%j == 0) {
      return false;
    }
  }

  return number > 2;
}

number/j 的成本在现代编译器中通常为零,因为他们看到 number%j 并有效地同时计算商和余数。因此达到了j &lt;= squareRoot 的限制 1) 无需昂贵的sqrt() 计算 2) 与sqrt() 的使用不同,对于大型number 是准确的。


使用匹配的说明符。 u,而不是 d 用于无符号类型。

// printf("Number %ld is a prime!\n", i);
printf("Number %lu is a prime!\n", i);

在这里使用全局i 是一种弱编码风格。建议重新编码并通过函数传递。


如需更多实质性改进,请查看 Sieve of Eratosthenes 并保留以前发现的素数列表并测试这些素数,而不是所有奇数。

Prime 测试是一门深奥的学科,包含许多更高级的想法。

【讨论】:

  • 感谢您的回答。不幸的是,我无法为唯一的赔率数字检查器获得更好的执行时间。我在这里制作了一个在线差异,以查看应用了您的建议的代码现在如何(一个版本仍在检查偶数,一个版本仅检查赔率)。由于不再使用 printf,我现在正在使用 time 命令测量性能。我使用了 45,000,000 的硬限制,因为我不希望这需要很长时间。两个版本都需要 49 秒才能再次完成,而赔率版本则慢了一秒。可以在这里找到差异:diffchecker.com/11b12TGD
  • @user-2147482617 对于 45,000,000,埃拉托色尼筛法可能是最快的
  • @user-2147482617 未优化的埃拉托色尼筛(45,000,000)耗时不到 2 秒。埃拉托色尼筛(1000,000,000)耗时 24 秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-13
  • 2015-08-12
  • 1970-01-01
  • 2017-01-17
  • 2016-09-19
  • 1970-01-01
  • 2014-10-16
相关资源
最近更新 更多