关于python:为什么Python比C慢很多

53次阅读

共计 2778 个字符,预计需要花费 7 分钟才能阅读完成。

话说为什么大家会集中探讨 GIL?在这里题主的标准线是一个按 bit 解决的单线程 DFS 啊……简直没有 GIL 施展的余地好么……

这个八皇后的 DFS,我的 C ++ 代码在不加某些评估性剪枝的状况下对 15 须要算 18s 左右(开 O2 大概 8.6 秒,与题主形容基本一致),然而能够确定的是你的解决方案里用了循环与递归。接下来须要剖析的无非是 Python 慢在哪个细节,以及是否改良的问题。

上面是两段用来测试的代码,首先是 Python 的:

class=”highlight”>

#!/usr/bin/env python3

import time

def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        rt = 0
        for j in range(n):
            col = 1 << j
            diag = 1 << (i - j + n - 1)
            tran = 1 << (i + j)
            if (col & cols) == 0 and (diag & diags) == 0 and (tran & trans) == 0:
                rt += calc(n, i+1, cols | col, diags | diag, trans | tran)
        return rt

if __name__ == '__main__':
    t = time.time()
    print(calc(13))
    print(time.time() - t)

以及 C ++ 代码:

#include <chrono>
#include <iostream>

using namespace std;

long calc(int n, int i = 0, long cols = 0, long diags = 0, long trans = 0) {if (i == n) {return 1;} else {
        long rt = 0;
        for (int j = 0; j < n; j++) {long col = (1 << j);
            long diag = (1 << (i - j + n - 1));
            long tran = (1 << (i + j));
            if (!(col & cols) && !(diag & diags) && !(tran & trans)) {rt += calc(n, i + 1, col | cols, diag | diags, tran | trans);
            }
        }
        return rt;
    }
}

int main() {auto t = chrono::system_clock::now();
    cout << calc(13) << endl;
    cout << (chrono::system_clock::now() - t).count() * 1e-6 << endl;
    return 0;
}

这里的 C ++ 代码没依照 OOP 去写,怎么简略怎么来吧……

测试机器配置是 Core i7 4870HQ,编译器用的 Clang++ 8.1.0,Python 解释器则是 CPython 3.6.0。没测试 15 的数据量只测试一下 13,因为 15 太费时间了……

因为这里压根不波及多线程问题,那基本上就跟 GIL 没有半毛钱关系了。

对于 n =13,C++ 代码跑了 0.48 秒。为了确保不是编译器轻轻干了活,我顺便打成了 -O0(实际上开 O2 能到 0.2 秒左右)。Python 跑了 24 秒。

对于这个例子,最间接的影响其实在于:Python 是逐句解释执行的,C++ 是先编译成本地代码,期间还有编译期的类型查看,不存在动静类型、动静查看,并且能够进行编译器优化。

之后应该考虑一下能不能进步一点点效率呢?

而后依据个别法则,Python 的循环很慢,咱们能够思考改成列表开展:

def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        return sum(
            [calc(n, i + 1, cols | (1 << j), diags | (1 << (i - j + n - 1)), trans | (1 << (i + j)))
                for j in range(n)
                if (cols & (1 << j)) == 0 and (diags & (1 << (i - j + n - 1))) == 0 and (trans & (1 << (i + j))) == 0
            ]
        )

理当速度更快,实时也验证了:这样的 Python 代码须要跑 18 秒左右。依然存在数量级的差别,并没有解决基本问题,然而阐明了一点,CPython 中 for loop 的实现其实一点都不快。

而后考虑一下,如果咱们应用其它解释器,特地是蕴含 JIT 的解释器,它将在执行过程中尝试将代码编译成本地二进制编码并执行,同时还能赋予一些额定优化,会不会好很多?

那么单纯地尝试一下 PyPy3(5.8.0-beta, Python 3.5.3),代码能有多快?

实际上,单纯的只是替换一下解释器,换成 PyPy 来做的话,本来这个 24s 的 Python 源码就只须要 1s 左右了。单单一个 JIT 能够使得性能晋升一个数量级,充分说明官网的 CPython 解释器的性能真心很烂……

PyPy 的 JIT 比较简单纯正,并不是很激进,然而同样的代码如果能借助更好的 JIT,以及更高性能的库,则能够体现出齐全不同的性能差。例如,如果应用 llvm 做 JIT,同时加上能应用一些成熟的数学库做优化。咱们晓得 NumPy 这样的 C 扩大可能很大水平进步 Python 做数值计算的性能,同样的咱们也能够用 Cython 或者间接用 C 写 Python 扩大来强化计算能力。然而人都是懒的,从新写代码切实是有些麻烦。对于 Python 这种生态弱小的玩意来说,如果你的计算代码中只是单纯的应用了 numpy 的简略构造以及 Python 本身的规范构造,应用 numba 可能是最简略疾速的方法。

#!/usr/bin/env python3

import time


from numba import jit


@jit
def calc(n, i=0, cols=0, diags=0, trans=0):
    if i == n:
        return 1
    else:
        rt = 0
        for j in range(n):
            col = 1 << j
            diag = 1 << (i - j + n - 1)
            tran = 1 << (i + j)

            if (col & cols) == 0 and (diag & diags) == 0 and (tran & trans) == 0:
                rt += calc(n, i+1, cols | col, diags | diag, trans | tran)
        return rt



if __name__ == '__main__':
    t = time.time()
    print(calc(13))
    print(time.time() - t)

这里只是很简略地退出了两行代码:从 numba 导入 jit,用 jit 装璜咱们的计算函数。这段代码的运行工夫间接就缩短到了 0.4s,和 C ++ 版本的 O0 编译后的程序速度简直一样。这还是思考到 JIT 须要预热的状况在内。这段代码,若是计算 15 的规模,只须要 6.5s 左右,甚至优于开 O2 的 C ++ 版本。

究其原因,JIT 不仅仅在运行过程中将代码转为本地机器码,同时还会尝试进行优化。如果用 cProfile 之类的玩意剖析一下运行过程,能够分明看到这个优化过程。

本次分享就到这啦, 如果对您有帮忙,麻烦点个赞和关注再走喔~ 谢谢浏览。

正文完
 0