簡體   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