为啥这个算法在 python 中的运行速度比在 C++ 中快得多?

Posted

技术标签:

【中文标题】为啥这个算法在 python 中的运行速度比在 C++ 中快得多?【英文标题】:Why does this algorithm work so much faster in python than in C++?为什么这个算法在 python 中的运行速度比在 C++ 中快得多? 【发布时间】:2016-02-06 18:17:17 【问题描述】:

我正在阅读 Robert Sedgewick 的“C++ 中的算法”,我得到了这个练习:通过将另一种编程语言中的算法减半来重写这个带有路径压缩的加权快速联合。

该算法用于检查两个对象是否连接,例如对于像 1 - 2、2 - 3 和 1 - 3 这样的条目,前两个条目创建新的连接,而在第三个条目中,1 和 3 已经连接,因为 3可以从 1:1 - 2 - 3 到达,因此第三个条目不需要创建新连接。

对不起,如果算法描述无法理解,英语不是我的母语。

所以这里是算法本身:

#include <iostream>
#include <ctime>

using namespace std;

static const int N 100000;

int main()

    srand(time(NULL));

    int i; 
    int j; 

    int id[N];  
    int sz[N]; // Stores tree sizes

    int Ncount; // Counts the numbeer of new connections 
    int Mcount; // Counts the number of all attempted connections

    for (i = 0; i < N; i++)
    
        id[i] = i;
        sz[i] = 1;
    

    while (Ncount < N - 1)
    
        i = rand() % N;
        j = rand() % N;

        for (; i != id[i]; i = id[i])
            id[i] = id[id[i]];

        for (; j != id[j]; j = id[j])
            id[j] = id[id[j]];

        Mcount++;

        if (i == j) // Checks if i and j are connected 
            continue;

        if (sz[i] < sz[j]) // Smaller tree will be 
                           // connected to a bigger one 
        
            id[i] = j;
            sz[j] += sz[i];
        
        else
        
            id[j] = i;
            sz[i] += sz[j];
        

        Ncount++;
    

    cout << "Mcount: " << Mcount << endl;
    cout << "Ncount: " << Ncount << endl;

    return 0;

我对python有一点了解,所以我选择它来做这个练习。这是得到的:

import random

N = 100000

idList = list(range(0, N))
sz = [1] * N

Ncount = 0
Mcount = 0

while Ncount < N - 1:

    i = random.randrange(0, N)
    j = random.randrange(0, N)

    while i is not idList[i]:
        idList[i] = idList[idList[i]]
        i = idList[i]

    while j is not idList[j]:
        idList[j] = idList[idList[j]]
        j = idList[j]

    Mcount += 1

    if i is j:
        continue

    if sz[i] < sz[j]:
        idList[i] = j
        sz[j] += sz[i]
    else:
        idList[j] = i
        sz[i] += sz[j]

    Ncount += 1

print("Mcount: ", Mcount)
print("Ncount: ", Ncount)  

但我偶然发现了这个有趣的细微差别:当我将 N 设置为 100000 或更多时,C++ 版本似乎比 python 慢很多 - 在 python 中完成算法的任务大约需要 10 秒,而 C++ 版本这样做太慢了,我只好将其关闭。

所以我的问题是:这是什么原因?这是因为 rand() % N 和 random.randrange(0, N) 的不同而发生的吗?还是我做错了什么?

如果有人能向我解释一下,我将不胜感激,在此先感谢!

【问题讨论】:

你是如何编译你的 C++ 代码的?确保启用编译器优化,例如通过使用 gcc 或 clang 编译 -O2-O3。未优化的 C++ 代码的性能毫无意义。 我在我的 QtCreator 中编译了它,我相信它使用 VS2013 编译器。我应该尝试在 Linux 上编译它吗? 如果您使用 VS,请确保选择 Release 构建,而不是 Debug 构建。 python 版本的结果是否正确? 我做到了,它似乎没有任何区别:(是的,我相信它。 【参考方案1】:

这些代码做不同的事情。

您必须将 python 中的数字与== 进行比较。

>>> x=100000
>>> y=100000
>>> x is y
False

可能还有其他问题,没有检查。您是否比较了应用程序的结果?

【讨论】:

谢谢,但我相信这对我没有任何影响。 @Goldutop:人们相信各种奇怪的东西。你相信什么没有区别。 Ncount 将在每次迭代中递增,只要您通过了留存整数的阈值。 确实如此,Mcount 得到了不同的结果。只要 python 代码没有产生相同(和正确)的结果,你的整个问题就毫无意义。 @Goldutop 这些都不重要。您的代码充满了错误,因此请先修复这些错误并比较两种工作算法。【参考方案2】:

如上所述,代码并不相同,尤其是在使用 is== 时。

看下面的Pyhton代码:

while i is not idList[i]:
    idList[i] = idList[idList[i]]
    i = idList[i]

这被评估 0 或 1 次。为什么?。因为如果while 第一次计算为True,那么i = idList[i] 在第二次通过时使条件True,因为现在i 肯定是isidList 中的一个数字

相当于c++

for (; i != id[i]; i = id[i])
     id[i] = id[id[i]];

这里的代码是检查 equality 而不是 presence 并且它运行的次数不固定为 0 或 1

所以是的 ... 使用 is== 会产生 巨大 的不同,因为在 Python 中您正在测试 实例相等性 并且 包含在,而不是在等价的意义上测试简单的相等

上面的 Python 和 C++ 的比较就像苹果和梨的比较。

注意:这个问题的简短回答是:Python 版本运行得更快,因为它比 C++ 版本运行得少很多

【讨论】:

感谢您的解释,但似乎我的编译器有问题,而不是我的代码,因为当我用 g++ 编译 C++ 版本时,它的运行速度比 python 版本快。 对不起,但i = idList[i] 并不暗示i == idList[i](或i != id[i] 为假,因为i 已随分配而改变,而您从idList[] 中提取的值是不同的。可以但不能。例如:i = 5 和 idList[5] 是 3。下一个检查是 3 != idList[3] 我们不知道 idList[3] 包含什么。所以是的,C++ 版本可以(并且将)运行更多次。OP(通过在 Linux 下重新编译而无需调试)仅证明 C++ 尽管有缺点,但速度要快得多。 当谈到 Python 中的“is”和“presence”时,是因为他从列表中分配值,然后检查刚刚分配的值是否“是”刚刚从列表中分配的值 - > 有效地检查存在。我可能不是 Guido,但我知道在上面代码的上下文中“是”在做什么。 @mementum 你对第一部分的看法是对的,但我仍然不相信你对is 的看法。 i != idList[i] 暗示 i is not idList[i](除了像float("nan")这样的非常奇怪的情况)所以如果C++代码执行另一个循环,它意味着Python代码也会如此。

以上是关于为啥这个算法在 python 中的运行速度比在 C++ 中快得多?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle ODP.NET 托管驱动程序在 64 位中的运行速度比在 32 位中慢 50-100%

为啥在 GPU 中执行方法的时间比在混合器项目中的 CPU 中执行的时间更多?

为啥这个 C++ 程序在 Windows 上比在 Linux 上慢?

为啥使用第一个阅读器 read() 运行第二个阅读器比在自己的阅读器上运行它运行得更快?

HTML5 Canvas 在 Firefox 上比在 Chrome 上更快!为啥?

为啥此 SIMD 代码运行速度比等效标量慢?