我在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倍?
答案 0 :(得分:2)
我的机器比你的慢,所以我还在等待结果...但由于我们在这里谈论平方和,所以让我们使用一些maths:< / p>
>>> n=1.0e9 - 1
>>> n**3/3 + n**2/2 + n/6
3.3333333283333336e+26
答案 1 :(得分: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
,即使它是整数;这将以最终结果的精度为代价,尽管 relative 错误在这里会非常小(但不是绝对错误)。
答案 2 :(得分:1)
不幸的是,在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 ++的原始速度,您可能需要调查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
这两种方法都具有显着减少计算时间的优势,但您的应用程序将不再是跨平台的。实际上,您需要为要部署到的每个平台分发已编译的本机模块。
答案 3 :(得分:0)
您有几个选择:
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会变慢,因为它被解释,计算绑定进程有开销。
答案 4 :(得分:0)
没有更快的解决方案 n比@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编译和其他开销,仅举几例 - 所以真正的体内性能比较可能会转向另一种方法的几个数量级,即体外方式&#34;慢&#34;