【问题标题】:Can I increase the performance of this Python code?我可以提高这个 Python 代码的性能吗?
【发布时间】:2014-10-06 10:49:49
【问题描述】:

我在 Python 中编写了以下简单循环。它使用浮点加法和乘法来对 0 到 1.0e9 - 1 之间的整数的平方求和。(这是一个玩具示例,但我相信它代表了我目前正在编写的代码)。

a = 0.0
i = 0.0
while i < 1.0e9:
    a += i * i
    i += 1.0
print a

在我的机器上,使用 32 位 CPython 2.7.8,这需要 400 秒才能运行。等效的 C++ 代码(如下)运行时间不到 2 秒,等效的 Go 代码运行时间不到 3。

double a = 0.0;
for(double i = 0.0; i < 1.0e9; i += 1.0) {
    a += i * i;
}
std::cout << a << std::endl;

由于我的代码需要分发给可能除了 CPython 之外没有安装任何其他东西的最终用户,我无法使用 PyPy 或 NumPy 来加速 Python 代码。

我还能做些什么来提高 Python 代码的性能,或者对于繁重的算术工作,CPython 通常会比 C++ 和 Go 慢 100 倍吗?

【问题讨论】:

  • 您使用浮点数表示始终为整数的值是否有特殊原因?
  • @WilliamMcBrine - 我对浮点性能感兴趣。在我的实际项目代码中,这些值很少是整数。
  • @PaulBaker 在这种情况下,Paul,对于这个社区来说,设计一个更具代表性的示例来用于 StackOverflow 主持的研究和讨论是公平的。这样您就可以获得正确的答案,但是对于一个主题,这不是您将在项目中使用的主题,从而导致您的问题所付出的所有努力都被浪费了。您可能想要定义您的任务目标和优先级,以便更好地匹配您的应用程序/问题域。

标签: python c++ performance optimization


【解决方案1】:

我的机器比你的慢,所以我还在等待结果……但既然我们在这里谈论平方和,让我们使用一些maths

>>> n=1.0e9 - 1
>>> n**3/3 + n**2/2 + n/6
3.3333333283333336e+26

【讨论】:

    【解决方案2】:

    如果你想通过 Cython 获得加速,你需要指定类型并使用 C 数据类型,而不是 python object 类型;

    pilot.pyx 文件,它定义了一个名为 pilot 的函数:

    def pilot(long long n):
        cdef long long i = 0
        cdef long double a = 0
    
        while i < n:
            a += i * i
            i += 1
    
        return a
    

    结果:

    In [11]: from pilot import pilot
    
    In [12]: int(pilot(1e9))
    Out[12]: 333333332833334250415587328
    
    In [13]: %timeit pilot(1e9)
    1 loops, best of 3: 1.22 s per loop
    

    请注意,为避免溢出,我必须使用long double 类型来表示a,即使它是整数;这将以最终结果的精度为代价,尽管这里的相对误差非常小(但不是绝对误差)。

    【讨论】:

    • 我在没有向 Python 代码添加任何类型声明的情况下使用 Cython 获得了极大的加速。
    • @NedBatchelder 见thisthis
    • 是的,使用类型声明会更快。但是您无需添加任何声明即可获得良好的加速效果。
    【解决方案3】:

    不幸的是,您在 cPython 中无能为力,但您可以使用 Cython 将关键部分编译为原生扩展。让我们考虑一下这个 sn-p:

    cdef double a = 0.0
    cdef double i = 0.0
    while i < 1.0e9:
        a += i * i
        i += 1.0
    print a
    

    注意cdef 部分,它为 Python 变量声明了一个静态类型。使用 Cython 编译将以几乎本机的速度执行,但可以省去手写 C 扩展的麻烦。

    说实话,写一个扩展并不难。因此,如果您想利用 C/C++ 的原始速度,您可能需要调查how to write one。例如,这将定义一个 modname.doit() 函数来执行计算:

    #include <Python.h>
    
    static PyObject * doit(PyObject *self, PyObject *args)
    {
        double a = 0.0;
        double i;
        for(i = 0.0; i < 1.0e9; i += 1.0) {
            a += i * i;
        }
        return Py_BuildValue("d", a);
    }
    
    static PyMethodDef methods[] = {
        {"doit",  doit, METH_VARARGS, "Do stuff with numbers."},
        {NULL, NULL, 0, NULL}
    };
    
    PyMODINIT_FUNC initmodname(void) {
        (void) Py_InitModule("beta", methods);
    }
    

    将其保存为modname.c 并将其编译为共享对象。例如,在 GNU/Linux 上:

    gcc -shared -pthread -fPIC -fwrapv -O3 -Wall -fno-strict-aliasing -I/usr/include/python2.7 -o modname.so modname.c
    

    这两种方法都具有显着减少计算时间的优势,但您的应用程序将不再是跨平台的。事实上,您需要为您将部署到的每个平台分发一个已编译的本机模块。

    【讨论】:

      【解决方案4】:

      你有几个选择:

      1) 使用 PyPy。在您的程序中,它的运行速度提高了大约 30 倍:

      $ time python sum.py
      3.33333332833e+26
      
      real    4m17.728s
      user    4m17.465s
      sys 0m0.107s
      $ time pypy sum.py
      3.33333332833e+26
      
      real    0m7.973s
      user    0m7.614s
      sys 0m0.049s
      

      2) 使用不同的算法。您可以使用代数更快地获得相同的答案。我知道您的真实代码与这个玩具示例不同,但仍有可能减少工作量。

      3) 使用 numpy,这就是它的用途。如果你在处理数组,numpy 会更快。

      4) 使用 Cython。我在您的 Python 代码上进行了尝试,它在 .936 秒 内运行,没有任何类型声明。

      5) 使用 C 或 Go。 CPython 会更慢,因为它是被解释的,计算绑定的进程会有开销。

      【讨论】:

      • 您是否愿意添加您的基准输出以进行精确(十进制.十进制)计算?这在数量上是可以比较的。
      • 普通 python 解释的精确计算在本地主机上采用 849 [usec],但主要与您在另一台机器上的数字无法比较。
      • 我以前没有看过 Cython,但这个结果令人印象深刻。我是否认为您使用 Cython 将我的 Python 转换为 C 代码,然后对 C 进行编译和基准测试?
      • 我以前从未使用过 Cython,我只是按照这些说明制作了一个已编译的扩展,并将其导入 Python 程序:docs.cython.org/src/quickstart/build.html
      • n.b.:在许多惊喜之后,人们宁愿不依赖演示示例的性能结果,因为现实世界的应用程序引入了更多演示代码中不存在的问题 --内存分配、垃圾收集、JIT 编译和其他开销,仅举几例——因此,真正的体内性能比较可能会转向另一种方法,即体外“更慢”的方法。跨度>
      【解决方案5】:

      任何解决方案都应该既快速又精确

      没有比@uselpa 提议的更快的解决方案n。

      结果的精度是另一个问题。

      0.000 849 秒的演示代码速度始终谨慎考虑,如上面的 cmets 所述。

      # PRECISION ...................................................................
      >>> import decimal
      >>> n = decimal.Decimal( '1e9')
      >>> n -= 1
      >>> n
      Decimal('999999999')
      
      >>> n**3/3 + n**2/2 + n/6
      Decimal('333333332833333333500000000.0')
      # DIFF                   ^^^
      #  int   33333333283333336..........
      # DIFF                 ^^^^^^^^^^^^^
      # pilot  333333332833334250415587328
      
      
      # SPEED ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
      >>> import zmq                                          # import for a Real-Time clock
      >>> aClk = zmq.Stopwatch()                              # having 0.000 001[sec]
      >>> aClk.start();n**3/3 + n**2/2 + n/6;usec=aClk.stop() # TimeDOMAIN resolution
      Decimal('333333332833333333500000000.0')                # Yes, a [usec] and
      >>> usec                                                # can measure down
      849L                                                    # to about 25[nsec]
                                                              # if setup correctly
      

      n.b.:在许多惊喜之后,人们宁愿不依赖演示示例的性能结果,因为实际应用程序会引入更多演示代码中不存在的问题-- 内存分配、垃圾收集、JIT 编译和其他开销,仅举几例 -- 所以真正的体内性能比较可能会转向另一种方法,即体外方法“更慢”

      【讨论】:

        猜你喜欢
        • 2020-07-11
        • 2019-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-27
        • 2013-10-15
        • 1970-01-01
        • 2017-10-13
        相关资源
        最近更新 更多