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

Confused as to why my algorithm is running slower than it should be

本文关键字:为什么 我的 算法 速度 运行      更新时间:2023-10-16

纯粹为了好玩,我决定写一个简单的算法来找到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中,并绘制它们。然而,由于某些原因,带有预检查的算法速度较慢。我几乎可以肯定它会更快。当我运行程序时,我故意关闭电脑上的每个程序,并以释放模式运行它。我已经在调试中测试了每个算法,它们确实都按预期工作。

这是我的数据。

x轴是我们要检查的质数(例如25000意味着我们要寻找2到25000之间的所有质数),y轴是得到所有质数的时间(以秒为单位)。

谁能解释一下为什么第二个算法,理论上应该减少许多计算,实际上更慢?

使用预检查的实现稍微慢一点的原因是,它需要做更多的工作来消除许多在内部循环的第一步之后就会消除的数字。

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

您可能看到的唯一一个稍微占上风的数字是5,但这些数字不像偶数那么常见,否则您的程序会损失CPU周期。

加快速度的更好方法是完全避免偶数:回想一下,3之后的所有素数都是6*k+16*k-1的形式之一。现在你的迭代速度几乎是原来的三倍!

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

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

因为它并没有省去很多计算。如果一个数字是偶数,在检查它是否能被2整除(这是你在循环中检查的第一个数字)时,会立即发现它。这比你在这里做的快多了:

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