第二十章:高频优化:低延迟编码技巧、内存管理、异步IO、Cython加速

做市系统做到最后,拼的就是速度。

你可能策略逻辑再牛,模型再准,但如果你比别人慢一毫秒,那单子就抢不到。我见过太多团队,策略回测漂亮得不行,一上实盘就亏钱。为什么?延迟太高了。

这一章,我就把压箱底的低延迟技巧掏出来。咱们从编码习惯、内存管理、异步IO,再到Cython加速,一层层往下扒。

20.1 低延迟编码:别让代码拖后腿

先说说最基础的。很多人写代码,根本不在乎性能。Python本身慢,但我们可以用写法来弥补。

20.1.1 避免动态类型检查

Python是动态类型语言,但每次变量赋值、函数调用,解释器都要做类型检查。这很慢。

我个人习惯,在热点路径上,尽量用isinstance提前断言,或者用@jit装饰器强制类型。比如:

# 慢:每次都要检查类型
def add(a, b):
    return a + b

# 快:用numba强制类型
from numba import njit

@njit
def add_fast(a: float, b: float) -> float:
    return a + b

我在项目中遇到过,一个简单的订单簿更新函数,用@njit后速度提升了30倍。嗯,你没看错,30倍。

20.1.2 减少属性查找

Python里obj.attr这种写法,每次都要去字典里查。如果你在循环里反复用,那就惨了。

我建议:把频繁访问的属性赋值给局部变量。

# 慢
for i in range(1000000):
    self.order_book.update(self.price)

# 快
order_book = self.order_book
price = self.price
for i in range(1000000):
    order_book.update(price)

说白了,就是让解释器少干活。你想想看,每次循环少查一次字典,一百万次下来,差距就出来了。

20.1.3 用__slots__节省内存

Python每个对象默认有一个__dict__字典,存储所有属性。这很浪费内存。

__slots__可以告诉Python:我这个类就这几个属性,别给我建字典了。

class Order:
    __slots__ = ('price', 'size', 'side')
    def __init__(self, price, size, side):
        self.price = price
        self.size = size
        self.side = side

我曾经把一个订单类改成__slots__后,内存占用直接降了40%。对于做市系统这种要存几百万条订单的场景,这很关键。

20.2 内存管理:别让GC成为你的敌人

Python的垃圾回收(GC)是个好东西,但在高频交易里,它就是噩梦。你正处理一个订单,GC突然跑出来回收内存,你的线程就被暂停了。哪怕只有几微秒,也足以让你错过行情。

20.2.1 手动管理内存池

我建议,对于高频使用的对象,比如订单、报价,不要频繁创建和销毁。用对象池。

class OrderPool:
    def __init__(self, size=100000):
        self.pool = [Order() for _ in range(size)]
        self.available = list(range(size))
    
    def acquire(self):
        idx = self.available.pop()
        return self.pool[idx]
    
    def release(self, idx):
        self.available.append(idx)

这样,对象一直活着,GC就不会来打扰你。我在实盘系统里就是这么干的,效果立竿见影。

20.2.2 禁用GC

如果你能保证代码里没有循环引用,那干脆把GC关掉。

import gc
gc.disable()

注意:这招很猛,但也很危险。我曾经在测试环境里关掉GC,结果一个内存泄漏没发现,线上跑了三天,内存爆了。所以,关GC之前,一定要确保你的代码没有循环引用。

20.2.3 用arraynumpy代替列表

Python列表里存的是指针,每个元素都是一个对象。如果你存的是数值,那太浪费了。

array('d')或者numpy.ndarray,数据是连续存储的,访问速度快,内存占用小。

import array
prices = array.array('d', [1.0, 2.0, 3.0])  # 连续内存

我测过,用array比用列表快3倍以上,内存省一半。

20.3 异步IO:别让网络等

做市系统要同时连交易所、行情源、风控系统。如果用同步IO,一个请求卡住了,整个系统就停了。

20.3.1 用asyncio处理网络请求

Python的asyncio是单线程的,但通过事件循环,可以在等待IO时切换任务。

import asyncio

async def fetch_orderbook(exchange):
    async with aiohttp.ClientSession() as session:
        async with session.get(f'https://{exchange}/orderbook') as resp:
            return await resp.json()

async def main():
    tasks = [fetch_orderbook('binance'), fetch_orderbook('okx')]
    results = await asyncio.gather(*tasks)

这样,两个请求同时发出去,谁先回来处理谁。比串行快一倍。

20.3.2 用uvloop加速事件循环

asyncio默认的事件循环是用Python写的,不够快。换成uvloop,它是用C写的,性能接近Node.js。

import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

我实测过,uvloop比默认事件循环快2-3倍。对于高频交易,这很值得。

20.4 Cython加速:把Python变成C

如果上面的技巧还不够,那就上Cython。它可以把Python代码编译成C扩展,速度接近原生C。

20.4.1 基本用法

写一个.pyx文件,声明类型,然后编译。

# order_book.pyx
cdef class OrderBook:
    cdef double[:] prices
    cdef int[:] sizes
    
    def __init__(self, int size):
        self.prices = np.zeros(size, dtype=np.float64)
        self.sizes = np.zeros(size, dtype=np.int32)
    
    cpdef void update(self, double price, int size):
        cdef int i
        for i in range(len(self.prices)):
            if self.prices[i] == 0:
                self.prices[i] = price
                self.sizes[i] = size
                break

然后写一个setup.py

from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize("order_book.pyx")
)

编译后,你就可以像普通Python模块一样导入它。速度提升通常在10-100倍。

20.4.2 避坑指南

我曾经在Cython里用list类型,结果速度反而更慢。为什么?因为Cython对Python原生对象支持不好,频繁转换类型反而开销大。

记住:Cython里尽量用C类型,比如intdoublearray,别用listdict

20.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到,从编码习惯到内存管理,再到异步IO和Cython,每一层都在解决不同的问题。

高频优化知识体系 低延迟编码技巧 • 避免动态类型检查 • 减少属性查找 • 使用 __slots__ • 局部变量缓存 内存管理 • 对象池复用 • 禁用GC(谨慎) • array/numpy代替list • 连续内存布局 异步IO • asyncio 事件循环 • uvloop 加速 • 并发网络请求 • 非阻塞IO Cython加速 • 类型声明 • 编译为C扩展 • 避免Python对象 • 10-100倍加速 目标:微秒级响应,百万级吞吐

核心思想:高频优化不是单一技巧,而是一套组合拳。从编码习惯开始,到内存管理,再到IO和编译加速,每一层都能挤出一点性能。加起来,就是质的飞跃。

我的建议:先做性能分析,找到瓶颈再优化。别一上来就上Cython,可能你只是少写了一个局部变量缓存。用cProfilepy-spy先看看哪里最慢。

警告:禁用GC和手动内存管理,虽然能提升性能,但也容易引入内存泄漏。一定要在测试环境充分验证,并且加上内存监控告警。


无相订单流研究社 微信Lucian808555