对为啥我的算法运行速度比它应该的慢感到困惑

Posted

技术标签:

【中文标题】对为啥我的算法运行速度比它应该的慢感到困惑【英文标题】:Confused as to why my algorithm is running slower than it should be对为什么我的算法运行速度比它应该的慢感到困惑 【发布时间】:2014-05-05 03:08:42 【问题描述】:

纯粹是为了好玩,我决定编写一个简单的算法来查找 2 和 x 之间的所有素数,其中 x 是你想要的任何值。我正在使用clock_t 来计算算法完成不同 x 值所需的时间。 (我去 x=0,然后是 25000,然后是 50000,然后是 75000,...,直到 1,000,000)。例如,当 x = 25000 时,我进入一个 for 循环,其中 i 从 2 变为 25000,并且对于 i 的每个值,我通过将其除以 2 和自身之间的每个数字来检查它是否是素数, 寻找余数 0。

算法如下:

vector<int> calcPrimesWithoutPrechecking(int upperLimit)

    vector<int> res;

    for(int i = 2; i <= upperLimit; i++)
    
        int currentNum = i;
        bool foundDivisible = false;
        for(int j = 2; j < currentNum; j++)
        
            if(currentNum % j == 0)
            
                foundDivisible = true;
                break;
            
        

        if(!foundDivisible)
        
            res.push_back(i);
        
    

    return res;

我想我可以通过检查我们当前正在测试的数字的最后一位来加快速度。如果那个数字是 0、2、4、5、6 或 8,那么我什至不必检查它是否是素数,因为我知道它不是(当然 2、3 和 5 是,所以这些都被处理了在一开始的时候)。我称之为预检查。这是带有预检查的算法:

vector<int> calcPrimesWithPrechecking(int upperLimit)

    vector<int> res;
    res.push_back(2);res.push_back(3);res.push_back(5);    
    for(int i = 6; i <= upperLimit; i++)
    
        bool foundDivisible = false;    
        int lastDig = i%10;
        if(lastDig == 0
            || lastDig == 2
            || lastDig == 4
            || lastDig == 6
            || lastDig == 8
            || lastDig == 5)
        
            foundDivisible = true;
            

        int currentNum = i;
        for(int j = 2; j < currentNum && !foundDivisible; j++)
        
            if(currentNum % j == 0)
            
                foundDivisible = true;
                break;
            
            

        if(!foundDivisible)
        
            res.push_back(i);
        
        
    return res;

我将结果输出到控制台,并将它们写入文本文件。然后我将时间复制到excel,并绘制它们。但是,由于某种原因,带有预检查的算法较慢。我几乎可以肯定它会更快。当我运行程序时,我会故意关闭计算机上的每个程序,并以发布模式运行它。我已经在调试中测试了每个算法,它们确实都按预期工作。

Here is my data.

x 轴是我们要检查的素数的数量(例如,25000 表示我们正在寻找 2 到 25000 之间的所有素数),y 轴是获得所有素数的时间(以秒为单位)。

有人可以解释为什么理论上应该消除许多计算的第二种算法实际上更慢吗?

【问题讨论】:

只遍历奇数会更容易,并且会稍微提高效率。将外循环更改为for(int i = 7; i &lt;= upperLimit; i += 2) 最佳的试除法是~ n^1.35..1.45和Eratosthenes的筛子~ n~1.05..1.1,在n产生的素数。您的算法是~ n^2 ~ x^2/(log x)^2,实际上您的数据在 100 万与 400K empirically~ x^1.92 【参考方案1】:

带有预检查的实现有点慢的原因是它需要做更多的工作来消除许多在内循环的第一步之后无论如何都会被消除的数字。

以数字8为例:预检查需要找到一个除法余数并进行五次比较才能消除它,而没有预检查的程序也消除了8,但只有一次除以二并与零进行比较。

5 是您可能会看到的唯一数字,但这些数字不像偶数那样常见,您的程序会在偶数上丢失 CPU 周期。

加快这一速度的更好方法是完全避免偶数:回想一下all prime numbers after 3 are either of the form 6*k+1 or 6*k-1。现在您可以将迭代速度提高近三倍!

另一件事是,您不需要检查候选素数平方根之后的除数(您能证明为什么会这样吗?)仅此更改就会给您带来巨大的改进。

最后,一个非常有用的技巧是存储您迄今为止发现的所有素数,并将它们用作您的试除数。这将大大提高你的内循环的速度。

【讨论】:

我正在尝试实现这一点,但它似乎认为某些数字是素数,而不是真正的素数。我为 i 的每个值检查 q = i/6 和 r = i%6,如果 r 是 0、2、3 或 4,我知道它不是素数,否则(如果是 1 或 5)它是主要。它主要是素数,但有一些非素数正在滑入(25、35、55、77,...) 循环应该是这样的:for (int i = 1 ; 6*i &lt; upperLimit ; i++) int cand1 = 6*i-1; int cand2 = 6*i+1; ... 然后使用内部for 循环测试两个候选者。 很抱歉,我没有完全关注... 我应该如何测试候选人?你所说的是我认为是结果的“候选人”。我不确定我应该对候选人进行什么测试。您发布的链接使 6(q+1)-1 和 6q+1 步骤看起来像是测试的最后一部分,而不是第一部分。 @xcdemon05 在您的代码中,i 是候选素数。您在外循环的每次迭代中测试一个候选素数。在我的循环中,6*i-16*i+1 是候选素数。您需要测试它们是否为素数,并将通过测试的素数添加到素数列表中。 @xcdemon05 试试for(int j = 0; ; j++) int p=res[j]; if( p &gt; c1/p ) res.push_back(c1); break; if( c1 % p == 0 ) break; .【参考方案2】:

因为它不会进行许多计算。如果一个数字是偶数,将在检查它是否能被 2 整除(这是您在循环中检查的第一个数字)时立即发现。这比你在这里做的要快得多:

int lastDig = i%10;
if(lastDig == 0
    || lastDig == 2
    || lastDig == 4
    || lastDig == 6
    || lastDig == 8
    || lastDig == 5)

【讨论】:

另外,如果成功,它不会跳过另一个 for 循环。 @LachlanEaston:确实如此。检查第二个版本的内循环的循环条件。 哇。这很有意义。我不敢相信这没有发生在我身上!

以上是关于对为啥我的算法运行速度比它应该的慢感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

对 gprof 输出感到困惑——调用太多?

对为啥我的 PHP 包含不起作用感到困惑 [关闭]

JS中的函数声明速度差异

Vector2.Normalize 函数,对对角线输出感到困惑

java操作xml运行速度慢?为啥呢?

磁盘清理以后速度为啥会更慢?