第1章:Python性能优化——向量化计算、并行处理、Cython加速、内存管理技巧

做量化交易,尤其是高频的基差套利,最怕什么?

怕行情来了,你的代码还在那吭哧吭哧算。我见过太多人,策略逻辑没问题,但一到实盘就卡死。说白了,Python 本身是慢的,但我们可以用工具把它“武装”起来。

今天这一章,我就把压箱底的性能优化手段掏出来。向量化、并行、Cython、内存管理,一个一个说清楚。

1. 向量化计算:别再用 for 循环了

先问个问题:你写策略时,是不是还在用 for 循环遍历 DataFrame?

如果是,赶紧改。Python 的 for 循环慢得离谱。我刚开始做回测时,一个 5 年的分钟数据,用 for 循环跑了 40 分钟。后来换成向量化,30 秒搞定。

核心思想:用 NumPy 或 Pandas 的内置函数,一次性操作整个数组,而不是逐元素操作。

举个例子,计算两个价格序列的价差:

# 慢的方式(别学)
spread = []
for i in range(len(price1)):
    spread.append(price1[i] - price2[i])

# 快的方式(向量化)
spread = price1 - price2  # 假设 price1, price2 是 NumPy 数组

为什么快?因为 NumPy 底层是 C 语言实现的,而且利用了 CPU 的 SIMD 指令集。你想想看,一次处理 1000 个元素,和循环 1000 次,哪个快?

避坑指南:我曾经在计算滚动窗口时,用了 rolling().apply() 配合自定义函数,结果慢到怀疑人生。后来换成 rolling().mean() 这种原生方法,速度提升了 100 倍。记住:能用原生函数的,就别自己写。

2. 并行处理:让多核 CPU 干活

向量化解决的是单核内的效率问题。但如果你有 8 核 CPU,只用一个核,是不是太浪费了?

我个人的习惯是,在回测多个品种组合时,用 multiprocessing 把任务分给不同的核。

import multiprocessing as mp

def backtest_one_pair(pair):
    # 假设这是你的回测函数
    return sharpe_ratio

pairs = [('RB.SHF', 'HC.SHF'), ('I.DCE', 'JM.DCE'), ...]
with mp.Pool(processes=4) as pool:
    results = pool.map(backtest_one_pair, pairs)

这里要注意:multiprocessing 会复制内存,如果你的数据太大,反而会慢。我建议只对计算密集型任务用并行,I/O 密集型的用多线程就行。

警告:Windows 下用 multiprocessing 要加 if __name__ == '__main__':,否则会无限递归。我当年被这个坑过,调试了一下午。

3. Cython 加速:把 Python 变成 C

如果向量化和并行还不够,那就上 Cython。

Cython 说白了,就是把 Python 代码编译成 C 扩展。你只需要加一些类型声明,就能获得接近 C 的速度。

举个例子,计算两个数组的协方差:

# cython_cov.pyx
def cython_cov(double[:] x, double[:] y):
    cdef int n = x.shape[0]
    cdef double sum_x = 0, sum_y = 0, sum_xy = 0
    cdef int i
    for i in range(n):
        sum_x += x[i]
        sum_y += y[i]
        sum_xy += x[i] * y[i]
    return (sum_xy - sum_x * sum_y / n) / (n - 1)

然后编译:

# setup.py
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("cython_cov.pyx"))

编译后,直接 import 就能用。速度提升 10-50 倍是常事。

个人经验:我一般只在最内层的循环用 Cython。比如计算期权 Greeks 时,那个循环要跑几百万次,用 Cython 改写后,从 5 秒降到 0.1 秒。但日常的数据处理,向量化就够了,没必要杀鸡用牛刀。

4. 内存管理技巧:别让内存爆了

做量化交易,数据量动不动就是几个 G。如果你不注意内存管理,程序会直接崩掉。

我总结了几条实用技巧:

  • dtype 压缩数据:默认的 float64 改成 float32,内存减半。精度损失可以忽略。
  • 及时释放大对象:del 删除不再用的 DataFrame,然后调用 gc.collect()
  • chunksize 分块读取:别一次性把 10G 的 CSV 读进内存。
# 分块读取示例
chunks = pd.read_csv('tick_data.csv', chunksize=100000)
for chunk in chunks:
    process(chunk)  # 处理完一块,内存自动释放

注意:我曾经在回测时,因为没释放中间变量,内存从 8G 涨到 32G,最后服务器直接 OOM 了。从那以后,我每写一段代码,都会用 memory_profiler 检查内存占用。

知识体系总览

下面这张图,是我自己总结的 Python 性能优化路线图。你可以把它当成一个检查清单:

Python 性能优化路线图 性能瓶颈 向量化计算 并行处理 Cython 加速 内存管理 NumPy 数组运算 Pandas 原生函数 避免 apply 循环 multiprocessing concurrent.futures 进程池管理 类型声明 编译为 C 扩展 内层循环优化 dtype 压缩 分块读取 及时释放

这张图里,从左到右,优化力度越来越大,但复杂度也越来越高。我的建议是:先用向量化,不够再加并行,还不行再上 Cython。别一上来就搞 Cython,那是最后的手段。

总结一下

性能优化这件事,说白了就是“用空间换时间,用硬件换效率”。

我个人习惯是:写代码时先追求正确性,然后 profiling 找出瓶颈,最后针对性优化。别一开始就想着优化,那叫过度设计。

嗯,这一章就到这里。记住:向量化是基本功,并行是加分项,Cython 是杀手锏,内存管理是保命符。

公众号:蓝海数据掘金营,微信 deep3321