為Python回測(cè)代碼提升十倍性能,具體做了哪些?

我們的回測(cè)程序是用Python寫的,因?yàn)槭褂肑upter Notebook顯示結(jié)果非常方便。但是,最近在一次運(yùn)行整個(gè)回測(cè)代碼時(shí),整整花了20分鐘才出現(xiàn)結(jié)果。于是,我們打算好好優(yōu)化一下。最終,性能提升10倍以上,耗時(shí)在1分29秒左右。

優(yōu)化流程

我們優(yōu)化主要分成兩部分,第一部分是程序內(nèi)部邏輯,第二部分是Python提速。所以,我們整個(gè)流程可以分成下面三步:

  • 第一步,Python性能檢測(cè)
  • 第二步,優(yōu)化程序內(nèi)部邏輯
  • 第三步,為Python提速

一、Python性能檢測(cè)

Python性能檢測(cè),我們使用的是line_profiler,它可以檢測(cè)代碼每行消耗的時(shí)間,非常方便好用。

1、line_profiler 安裝

使用pip安裝就行了

$ pip3 install line_profiler

2、line_profiler使用

首先,在需要測(cè)試的函數(shù)中加上修飾器@profile

例如:

@profile
def max_drawdown(data_list):
    """
    求最大回撤, 回撤公式=max(Di-Dj)/Di
    """
    max_return = 0
    for i, v1 in enumerate(data_list):
        for j, v2 in enumerate(data_list[i + 1:]):
            if v1 == 0:
                continue
            tmp_value = (v1 - v2) / v1
            if tmp_value > max_return:
                max_return = tmp_value
    return max_return

然后,運(yùn)行腳本,生成.lprof文件

$ kernprof -l lineProfilerDemo.py

最后,查看各行代碼運(yùn)行的時(shí)間

$ python3 -m line_profiler lineProfilerDemo.py.lprof

Timer unit: 1e-06 s

Total time: 0.000475 s
File: lineProfilerDemo.py
Function: max_drawdown at line 4

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     4                                           @profile
     5                                           def max_drawdown(data_list):
     6                                               """
     7                                               求最大回撤, 回撤公式=max(Di-Dj)/Di
     8                                               """
     9         1         15.0     15.0      3.2      max_return = 0
    10        11          8.0      0.7      1.7      for i, v1 in enumerate(data_list):
    11        55         56.0      1.0     11.8          for j, v2 in enumerate(data_list[i + 1:]):
    12        45        346.0      7.7     72.8              if v1 == 0:
    13                                                           continue
    14        45         25.0      0.6      5.3              tmp_value = (v1 - v2) / v1
    15        45         23.0      0.5      4.8              if tmp_value > max_return:
    16         3          1.0      0.3      0.2                  max_return = tmp_value
    17         1          1.0      1.0      0.2      return max_return

最后% Time那一列就是本行代碼消耗的時(shí)間占比。

注意:每次只能檢測(cè)一個(gè)函數(shù),如果檢測(cè)某一行代碼執(zhí)行比較慢??梢詫profile移動(dòng)到這一行代碼對(duì)應(yīng)的方法,然后繼續(xù)往內(nèi)部檢測(cè)。

3、在Jupyter Notebook中使用

先使用%load_ext line_profiler加載,然后使用lprun -s -f語句打印結(jié)果,具體使用如下:

%load_ext line_profiler

import numpy as np
def max_drawdown(data_list):
    """
    求最大回撤, 回撤公式=max(Di-Dj)/Di
    """
    max_return = 0
    for i, v1 in enumerate(data_list):
        for j, v2 in enumerate(data_list[i + 1:]):
            if v1 == 0:
                continue
            tmp_value = (v1 - v2) / v1
            if tmp_value > max_return:
                max_return = tmp_value
    return max_return

value_array = np.random.randint(1, high=10, size=10)
%lprun -s -f max_drawdown max_drawdown(value_array)

效果如下:


image-20190612160711154.png

二、優(yōu)化程序內(nèi)部邏輯

我們的回測(cè)代碼,主要分為數(shù)據(jù)讀取、數(shù)據(jù)處理、交易邏輯、回測(cè)結(jié)果顯示四部分。其中,我們主要優(yōu)化了三方面的代碼:

  • 優(yōu)化數(shù)據(jù)讀取性能
  • 簡(jiǎn)化數(shù)據(jù)
  • 改進(jìn)算法

1、優(yōu)化數(shù)據(jù)讀取性能

最初,我們讀取數(shù)據(jù)是這樣的:


image.png

最終,優(yōu)化之后讀取數(shù)據(jù)過程是這樣的:


image.png

具體流程如下:

第一次回測(cè)的時(shí)候,我們會(huì)到局域網(wǎng)的Mongodb上讀取數(shù)據(jù),同時(shí)緩存一份json文件在本地。并且,把對(duì)應(yīng)的數(shù)據(jù)緩存一份在Jupyter notebook的內(nèi)存里面。

那么,當(dāng)下一次回測(cè)的時(shí)候:

  • 如果Jupter notebook未重啟,我們直接從內(nèi)存中拿取數(shù)據(jù),這個(gè)就不用耗費(fèi)時(shí)間重新讀取
  • 如果Jupter notebook重啟了,我們就從本地json文件中讀取數(shù)據(jù),這個(gè)也會(huì)比從局域網(wǎng)Mongodb中讀取快很多
  • 如果,json文件有24小時(shí)以上沒有更新了,我們則再次從局域網(wǎng)Mongodb中讀取,并更新本地緩存

2、簡(jiǎn)化數(shù)據(jù)

在檢測(cè)處理數(shù)據(jù)的時(shí)候,發(fā)現(xiàn)計(jì)算天的指標(biāo)時(shí)間基本可以忽略,最主要的時(shí)間放在遍歷分鐘數(shù)據(jù)的for循環(huán)里面。其實(shí),這個(gè)for循環(huán)代碼特別簡(jiǎn)單,但是因?yàn)榉昼姅?shù)據(jù)有超過9百萬條,導(dǎo)致處理它成為了主要花費(fèi)時(shí)間。那我們是否可以簡(jiǎn)化數(shù)據(jù),優(yōu)化這個(gè)性能?

我們的分鐘數(shù)據(jù)主要有:high、low、open、close、volume、timestamp、date_time、frame、symbol等數(shù)據(jù),但是我們暫時(shí)用到的只有high、low、open、close、timestamp,于是最終我們只保留了6個(gè)字段high、low、open、close、timestamp、volume,volume怕以后可能會(huì)用到。

同時(shí),除了減少字段以為,我們將本來存儲(chǔ)是字典的數(shù)據(jù)結(jié)構(gòu),改成數(shù)組,以open、close、high、low、volume、timestamp的固定順序存儲(chǔ)。

這是因?yàn)?,我們?cè)跍y(cè)試性能的時(shí)候發(fā)現(xiàn),數(shù)組不管是讀取數(shù)據(jù)還是遍歷,都比字典快,測(cè)試代碼如下:

import time

def test_dic1(info):
    a, b = info['name'], info['height']

def test_dic2(info):
    for key in info:
        pass

def test_array1(info):
    a, b = info[0], info[1]

def test_array2(info):
    for value in info:
        pass

start_timestamp = time.time()
for i in range(10**6):
    test_array1(['Jack', 1.8, 1])

print("取元素?cái)?shù)組消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

start_timestamp = time.time()
for i in range(10**6):
    test_dic1({
        'name': 'Jack',
        'height': 1.8,
        'sex': 1
    })
print("取元素字典消耗時(shí)間: {:.4f}秒\n".format(time.time() - start_timestamp))

start_timestamp = time.time()
for i in range(10**6):
    test_array2(['Jack', 1.8, 1])

print("遍歷數(shù)組消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

start_timestamp = time.time()
for i in range(10**6):
    test_dic2({
        'name': 'Jack',
        'height': 1.8,
        'sex': 1
    })
print("遍歷字典消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

結(jié)果:

$ python3 testDicAndArray.py 
取元素?cái)?shù)組消耗時(shí)間: 0.2128秒
取元素字典消耗時(shí)間: 0.2779秒

遍歷數(shù)組消耗時(shí)間: 0.2346秒
遍歷字典消耗時(shí)間: 0.2985秒

比較之后,發(fā)現(xiàn)數(shù)組的速度比字典快了20%以上。

最終,在稍微犧牲了一點(diǎn)可讀性,簡(jiǎn)化了數(shù)據(jù)之后,我們的性能提升了很大,主要有三方面:

  • 本地的json文件體積變成了以前的1/3,讀取速度提升了將近80%
  • 處理數(shù)據(jù)性能得到提升
  • 交易邏輯性能得到提升

3、改進(jìn)算法

(1)for循環(huán)內(nèi)部數(shù)據(jù)提取到循環(huán)外面

通過line_profile檢測(cè),我發(fā)現(xiàn)有個(gè)判斷語句時(shí)間占比比較大,代碼邏輯大概如下所示:

for i in range(10000000):
    if i < self.every_coin_max_cash_unit() and (self.max_total_cash_unit() == -1 or total_cash_unit <= self.max_total_cash_unit()):
        pass

然后分析之后,我發(fā)現(xiàn)every_coin_max_cash_unit()方法和max_total_cash_unit()方法的值只是2個(gè)配置參數(shù),配置好了就不會(huì)再變化。

于是,使用兩個(gè)變量緩存下,放到for循環(huán)外面,優(yōu)化成了下面這樣:

every_coin_max_cash_unit = self.every_coin_max_cash_unit()
max_total_cash_unit = self.max_total_cash_unit()
# 當(dāng)為-1時(shí), 給1個(gè)很大值,大于1000就夠了
max_total_cash_unit = 100000 if max_total_cash_unit == -1 else max_total_cash_unit

for i in range(10000000):
    if i < every_coin_max_cash_unit and total_cash_unit <= max_total_cash_unit:
        pass

像這種優(yōu)化,至少優(yōu)化了3處。例如,在for循環(huán)內(nèi)部獲取數(shù)組長(zhǎng)度等等。

(2)最大回撤,將O(n2)算法優(yōu)化成O(n)

在優(yōu)化完測(cè)試的數(shù)據(jù)后,我們嘗試跑整個(gè)數(shù)據(jù),發(fā)現(xiàn)回測(cè)顯示結(jié)果特別慢。然后使用line_profile去檢測(cè),一層一層往里面查的時(shí)候,發(fā)現(xiàn)計(jì)算最大回撤的時(shí)候特別耗時(shí)。下面是以前計(jì)算回撤的代碼:

def max_drawdown(data_list):
    """
    求最大回撤, 回撤公式=max(Di-Dj)/Di
    """
    max_return = 0
    for i, v1 in enumerate(data_list):
        for j, v2 in enumerate(data_list[i + 1:]):
            if v1 == 0:
                continue
            tmp_value = (v1 - v2) / v1
            if tmp_value > max_return:
                max_return = tmp_value
    return max_return

它的時(shí)間復(fù)雜度是O(n2),而收益數(shù)據(jù)有28000多條(每小時(shí)記錄一次),那計(jì)算數(shù)據(jù)量達(dá)到了8.4億次,這樣就比較大了。

參考了【Python量化】O(n)復(fù)雜度實(shí)現(xiàn)最大回撤的計(jì)算的思路后,我們將代碼優(yōu)化成O(n),代碼示例如下:

def new_max_drawdown(data_array):
    """
    求最大回撤
    :param array:
    :return:
    """
    drawdown_array = []
    max_value = data_array[0]
    for value in data_array:
        # 當(dāng)前值超過最大值, 則替換值, 并且當(dāng)前是波峰, 所以當(dāng)前值對(duì)應(yīng)最大回撤為0
        if value > max_value:
            max_value = value
            drawdown_array.append(0)
        else:
            drawdown_value = (max_value - value) / max_value
            drawdown_array.append(drawdown_value)

    return max(drawdown_array)

性能,又得到提升。

(3)緩存好計(jì)算結(jié)果,直接傳遞參數(shù)

這方面,和第一點(diǎn)道理有點(diǎn)類似,這里主要有兩個(gè)例子:

  • 我們的回測(cè)結(jié)果是比較豐富的,不單單會(huì)計(jì)算每年的收益率、夏普值、索提諾值等等,還會(huì)精確計(jì)算到每個(gè)月。雖然,最大回撤的算法優(yōu)化了,這方面得到很大的提升,但是我們覺得還不是極限。在使用line_profile檢測(cè)之后,發(fā)現(xiàn)計(jì)算每個(gè)月結(jié)果花費(fèi)的時(shí)間占比比較大。分析之后,才知道每個(gè)月都會(huì)重新再計(jì)算一下市值、收益率等等,這個(gè)其實(shí)完全可以在最外面計(jì)算好,然后取某一段數(shù)據(jù),當(dāng)做參數(shù)傳遞過去。
  • 我們回測(cè)代碼剛開始在回測(cè)數(shù)據(jù)的時(shí)候,是分成每年、2013至2019、2016至2018、2018至2019多個(gè)時(shí)間段回測(cè),然后每回測(cè)一次,都會(huì)重新再處理一遍數(shù)據(jù)。這種重復(fù)計(jì)算,造成了好十幾秒的浪費(fèi)。于是,我們將整體代碼邏輯改一下,先一次性將回測(cè)數(shù)據(jù)處理好,然后再分別取出來使用,不用再重新處理。

三、為Python提速

Python語言提速,我們分別嘗試了三種方案:

  • Cython
  • Pypy
  • Numba

1、Cython

Cython將Python代碼翻譯成C語言版本,然后生成Python擴(kuò)展模塊,供我們使用。

Cython安裝很簡(jiǎn)單,pip3 install Cython就行了。

怎么使用呢?

例如我們有一個(gè)Python腳本test_array.py,內(nèi)容如下:

def test_array(info):
    for i in range(100):
        sum = 0
        for value in info:
            if value % 2 == 0:
                sum += value
            else:
                sum -= value

我們?cè)?code>test.py腳本中引入test_array模塊,代碼如下:

import time
from test_array import test_array

start_timestamp = time.time()
test_array(range(1, 100000))
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

我們執(zhí)行test.py腳本,獲得了下面結(jié)果:

$ python3 test.py
消耗時(shí)間: 1.0676秒

下面,我們使用Cython為它提速。

首先,我們將test_array.py中的內(nèi)容copy到另一個(gè)文件test_array1.pyx中。

然后,我們創(chuàng)建一個(gè)setup.py文件,它的內(nèi)容如下:

from distutils.core import setup
from Cython.Build import cythonize

setup(
    name='test_array',
    ext_modules=cythonize('test_array1.pyx'),
)

之后,我們執(zhí)行下面語句編譯腳本

python3 setup.py build_ext --inplace

這一步,它為我們生成了test_array1.ctest_array1.cpython-37m-darwin.so文件

接著一步,我們?cè)?code>test.py中導(dǎo)入test_array1模塊

import time
# from test_array import test_array
from test_array1 import test_array

start_timestamp = time.time()
test_array(range(1, 100000))
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

最后,我們執(zhí)行test.py

$ python3 test.py
消耗時(shí)間: 0.4671秒

什么代碼都沒有改,我們就為Python提升2倍多的速度

不過,根據(jù)cython建議,我們?cè)诖_定數(shù)據(jù)類型情況下,可以使用cdef先確定數(shù)據(jù)類型,test_array1.pyx修改后代碼如下:

cpdef test_array(info):
    cdef int i, sum, value
    for i in range(100):
        sum = 0
        for value in info:
            if value % 2 == 0:
                sum += value
            else:
                sum -= value

執(zhí)行test.py

$ python3 test.py
消耗時(shí)間: 0.1861秒

又提升了2倍的速度,相比于原生的python代碼,提升了將近6倍的速度。

其實(shí),若是我們傳遞的數(shù)組確定類型,那么速度還能提升,test_array1.pyx代碼修改如下:

cpdef test_array(int[:] info):
    cdef int i, sum, value, j
    cdef int n = len(info)

    for i in range(100):
        sum = 0
        for j in range(n):
            value = info[j]
            if value % 2 == 0:
                sum += value
            else:
                sum -= value

此時(shí),我們test.py也需要修改下,傳遞的數(shù)據(jù)是array類型,如下:

import time
# from test_array import test_array
from test_array1 import test_array
import array

start_timestamp = time.time()
a_array = array.array('i', range(1, 100000))
test_array(a_array)
# test_array(range(1, 100000))
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

最后,執(zhí)行test.py,看下效果

$ python3 test.py
消耗時(shí)間: 0.0138秒

相比于前面代碼,提升了10多倍,比原生的python代碼,提升了75倍

總結(jié):Cython完全兼容Python,也支持對(duì)C語言代碼的支持。不過,它對(duì)代碼優(yōu)化方面,需要下一些功夫才能達(dá)到極致效果。

2、Pypy

Pypy是Python語言的JIT編譯器,它的運(yùn)行速度在某些方面比原生更快。

安裝Pypy3

$ brew install pypy3
??  /usr/local/Cellar/pypy3/7.0.0: 5,776 files, 135.5MB

下面,我們?cè)囋噋ypy的速度,執(zhí)行前面test.py的原生python腳本,內(nèi)容如下:

import time
from test_array import test_array

start_timestamp = time.time()
test_array(range(1, 100000))
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

結(jié)果如下:

$ pypy3 test.py

消耗時(shí)間: 0.0191秒

不需要任何代碼修改,它將近達(dá)到我們上面Cython最終效果,比原生的Python快54倍

由于它是Python的另外一套編譯器了,需要重新安裝依賴環(huán)境,下面是我們安裝過程遇到的一些問題:

(1)pypy安裝numpy后,運(yùn)行報(bào)錯(cuò),錯(cuò)誤信息:

Traceback (most recent call last):
  File "/usr/local/Cellar/pypy3/7.0.0/libexec/site-packages/numpy/core/init.py", line 40, in <module>
    from . import multiarray
  File "/usr/local/Cellar/pypy3/7.0.0/libexec/site-packages/numpy/core/multiarray.py", line 12, in <module>
    from . import overrides
  File "/usr/local/Cellar/pypy3/7.0.0/libexec/site-packages/numpy/core/overrides.py", line 6, in <module>
    from numpy.core._multiarray_umath import (
ImportError: dlopen(/usr/local/Cellar/pypy3/7.0.0/libexec/site-packages/numpy/core/_multiarray_umath.pypy3-70-darwin.so, 6): Symbol not found: _PyStructSequence_InitType2
  Referenced from: /usr/local/Cellar/pypy3/7.0.0/libexec/site-packages/numpy/core/_multiarray_umath.pypy3-70-darwin.so
  Expected in: dynamic lookup

后來發(fā)現(xiàn)豆瓣源過時(shí)了,于是換成官方的源,重新安裝就行了:

$ vim ~/.pip/pip.conf

[global]
#index-url = https://pypi.douban.com/simple/
#index-url = http://mirrors.aliyun.com/pypi/simple/
index-url = https://pypi.python.org/pypi

換回官方源,執(zhí)行pip_pypy3 install numpy安裝的的numpy版本是numpy-1.16.4,果然就沒問題了。豆瓣源還是numpy-1.16.3

(2)沒有安裝模塊playhouse,具體信息:

Traceback (most recent call last):
  File "BackTestFunction.py", line 1, in <module>
    from utils import helper
  File "/Users/liuchungui/Sites/python/BackTest/utils/helper.py", line 3, in <module>
    from playhouse.shortcuts import model_to_dict
ModuleNotFoundError: No module named 'playhouse'

原來是因?yàn)闆]有安裝peewee這個(gè)庫(kù)導(dǎo)致的,重新安裝:pip_pypy3 install peewee

(3)安裝pandas失敗,報(bào)錯(cuò)信息如下:

  Downloading https://files.pythonhosted.org/packages/b2/4c/b6f966ac91c5670ba4ef0b0b5613b5379e3c7abdfad4e7b89a87d73bae13/pandas-0.24.2.tar.gz (11.8MB)
    100% |████████████████████████████████| 11.8MB 401kB/s 
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/qc/z1r6s9rs3h7dtlthq1dstq840000gn/T/pip-install-9j2r7tk5/pandas/setup.py", line 438, in <module>
        if python_target < '10.9' and current_system >= '10.9':
      File "/usr/local/Cellar/pypy3/7.0.0/libexec/lib-python/3/distutils/version.py", line 52, in __lt__
        c = self._cmp(other)
      File "/usr/local/Cellar/pypy3/7.0.0/libexec/lib-python/3/distutils/version.py", line 335, in _cmp
        if self.version == other.version:
    AttributeError: 'LooseVersion' object has no attribute 'version'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/qc/z1r6s9rs3h7dtlthq1dstq840000gn/T/pip-install-9j2r7tk5/pandas/

原來是安裝最新版本會(huì)報(bào)錯(cuò),所以安裝0.23.4,命令如下:

$ pip_pypy3 install pandas==0.23.4

參考:centos7 pypy python3.6版本安裝編譯及numpy,pandas安裝

(4)安裝talib

pip_pypy3 install TA-Lib

(5)Pypy在Jupter Notebook使用

只需要改下kernel就行了,操作如下:

復(fù)制一份kernel,命名為pypy3

cp ~/Library/Jupyter/kernels/python3/ ~/Library/Jupyter/kernels/pypy3

編輯kernel.json,使用pypy3

$ vim kernel.json

{
 "argv": [
  "/usr/local/bin/pypy3",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "pypy3",
 "language": "python"
}

保存之后,我們?cè)贘upter Notebook選擇kernel時(shí),選擇pypy3就行了。

總結(jié):Pypy不需要修改代碼就能運(yùn)行并得到加速,這方面很方便。不過,它也有缺點(diǎn),它不兼容Cpython,在科學(xué)計(jì)算領(lǐng)域中兼容性也不行,有時(shí)候性能反而更差。例如,我曾經(jīng)準(zhǔn)備優(yōu)化讀取文件,默認(rèn)情況下一個(gè)一個(gè)讀取文件需要花費(fèi)12秒;使用多線程沒有效果(因?yàn)镚IL,其實(shí)還是一個(gè)線程);使用多進(jìn)程只需要花費(fèi)4.4秒,提升效果明顯;使用多進(jìn)程+Pypy,檢查了進(jìn)程,根本沒有用到多進(jìn)程,而速度卻反而變慢了。還有,在使用ujson解析讀取json文件時(shí),速度也反而慢了。

3、Numba

Numba是一個(gè)JIT編譯器,可以在運(yùn)行時(shí)將Python代碼編譯成機(jī)器代碼,和Pypy類似,不過它對(duì)CPython兼容性更好。

不過,不同于Pypy,它是用裝飾器@jit()來為指定函數(shù)加速。

test_array.py代碼如下:

from numba import jit

@jit()
def test_array(info):
    for i in range(100):
        sum = 0
        for value in info:
            if value % 2 == 0:
                sum += value
            else:
                sum -= value

test.py傳遞numpy數(shù)組參數(shù),如下:

import time
from test_array import test_array
import numpy as np

start_timestamp = time.time()
a = np.array(range(1, 100000))
test_array(a)
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

執(zhí)行結(jié)果如下:

$ python3 test.py
消耗時(shí)間: 0.2292秒

而若是傳遞Numpy參數(shù)數(shù)組,不使用Numba加速,耗時(shí)是6.6148,所以它為Numpy加速將近30倍。

不過,Numba對(duì)原生的list不會(huì)加速,反而會(huì)讓代碼運(yùn)行更慢。例如,上面test.py代碼如下:

import time
from test_array import test_array

start_timestamp = time.time()
test_array(range(1, 100000))
print("消耗時(shí)間: {:.4f}秒".format(time.time() - start_timestamp))

執(zhí)行結(jié)果如下:

$ python3 test.py
消耗時(shí)間: 1.6948秒

總結(jié):Numba使用@jit()裝飾器來進(jìn)行加速,它可以指定函數(shù)進(jìn)行加速,但是不能整體加速,使用起來沒有Pypy方便,而且對(duì)普通的Python代碼沒有加速效果。不過,它的優(yōu)勢(shì)在于支持Numpy等科學(xué)計(jì)算領(lǐng)域的框架,對(duì)Numpy有更一層的加速效果。

總結(jié)

最終我們選擇了使用Pypy,理由如下:

1、Pypy不用我們修改任何代碼,不像Cython那樣麻煩,比Numba也方便

2、Pypy對(duì)純Python代碼加速效果很好,我們基本上都是純Python代碼,而且整體加速效果很明顯

3、Pypy缺點(diǎn)是數(shù)據(jù)讀取比原生更慢,而且不能使用ujson這種C語言框架來加速。不過在簡(jiǎn)化數(shù)據(jù)之后,暫時(shí)還能接受。而且,后期可以考慮通過Pypy的腳本調(diào)用Python腳本,然后獲取數(shù)據(jù)來進(jìn)行提速。

參考

優(yōu)化 Python 性能:PyPy、Numba 與 Cython,誰才是目前最優(yōu)秀的 Python 運(yùn)算解決方案?

干貨 | Python 性能優(yōu)化的20條招數(shù)

Working with Python arrays

用Cython寫高性能的數(shù)組操作

使用PyPy性能調(diào)優(yōu)

Unable to install numpy with pypy3 on MacOS

Python更快的解析JSON大文件

如何看待 PyPy 與 Pyston 的未來?

Python · numba 的基本應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容