繁体   English   中英

我可以提高此Python代码的性能吗?

[英]Can I increase the performance of this Python code?

我用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倍吗?

我的机器比您的机器慢,因此我仍在等待结果...但是由于我们在这里谈论平方和,所以让我们使用一些数学运算:

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

如果要使用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即使它是整数。 尽管相对误差在这里很小(但不是绝对误差),但这将以最终结果的精度为代价。

不幸的是,在cPython中您无能为力,但是您可以使用Cython将关键部分编译为本地扩展。 让我们考虑以下代码段:

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 ++的原始速度,则可能需要研究如何编写一个 例如,这将定义一个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

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

您有几种选择:

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被解释,因此它会变慢,因为计算绑定的过程会产生开销。

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

没有比@uselpa建议的解决方案更快的解决方案

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

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

# 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

nb :出乎许多意外之后, 人们宁愿不依赖演示示例的性能结果 ,因为现实世界中的应用程序引入了演示代码中不存在的更多问题-内存分配,垃圾回收,JIT编译和其他开销只是其中的几个-因此,实际的体内性能比较可能会向另一个方法转移几个数量级,这是“慢”的体外方法

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM