问题描述
我的程序中会针对每一个股票调用一次 get_ticks。
每次使用之后,即使通过 del ticks,也很容易导致内存占用过大,被强制杀掉。所以怀疑是不是 get_ticks本身有问题,悄悄摸摸占用了内存没释放?
解决方案
在 JoinQuant 聚宽平台中,频繁调用 get_ticks 导致内存占用过大并被系统强制杀掉进程(通常平台限制内存为 3G),是一个非常常见的问题。这通常并不是 get_ticks 本身存在恶意的内存泄漏,而是由 Python 的内存管理机制以及平台的底层缓存机制共同导致的。
为什么 del 之后内存依然没有释放?
- 平台的底层缓存机制:为了加快回测和运行速度,JoinQuant 默认开启了数据缓存。当你调用
get_ticks时,底层可能会将这些庞大的 tick 数据缓存起来,导致即使你在应用层del了变量,底层缓存依然持有对这些数据的引用,内存无法释放。 - Python 的垃圾回收(GC)机制:Python 使用引用计数和分代回收机制。
del ticks仅仅是删除了变量名对对象的引用,如果存在循环引用或底层缓存引用,对象不会被销毁。此外,Python 的垃圾回收并不总是实时的,在密集循环中可能来不及回收就触发了平台的内存上限。 - Tick 数据本身极其庞大:Tick 数据包含买五卖五等大量盘口信息,如果股票池较大或获取的时间跨度较长,瞬间的内存峰值极高。
解决方案与优化建议
要彻底解决这个问题,建议结合以下几种方法对代码进行优化:
1. 关闭系统缓存(最关键)
在策略的 initialize 函数中调用 disable_cache()。这会关闭系统的默认缓存机制,防止底层悄悄囤积数据。注意:关闭缓存可能会导致策略整体运行速度有所下降,但能有效控制内存。
def initialize(context):
# 关闭缓存,防止底层缓存大量 tick 数据导致内存超限
disable_cache()
# 其他初始化代码...
2. 手动触发垃圾回收
在循环获取每只股票的 tick 数据并在处理完毕后,除了使用 del,还可以引入 gc 模块,手动强制进行垃圾回收。
import gc
def handle_data(context, data):
stocks = get_index_stocks('000300.XSHG')
for stock in stocks:
# 获取 tick 数据
ticks = get_ticks(stock, end_dt=context.current_dt, count=100)
# ... 进行你的数据处理逻辑 ...
# 处理完毕后,删除变量并强制回收内存
del ticks
gc.collect()
3. 优化 get_ticks 的参数
- 使用
df=False:默认情况下df=False返回的是numpy.ndarray,这比pandas.DataFrame(df=True) 的内存占用要小得多。如果不需要 DataFrame 的高级特性,尽量使用 numpy 数组。 - 按需获取字段 (
fields):默认会返回很多字段(如买五卖五等)。如果你只需要最新价和成交量,可以通过fields参数限制返回的数据列,大幅减少内存开销。
# 仅获取时间和最新价,且返回 numpy 数组
ticks = get_ticks(stock, end_dt=context.current_dt, count=100, fields=['time', 'current'], df=False)
4. 减少单次获取的数据量
尽量避免一次性获取过长时间跨度的 tick 数据。如果必须处理大量数据,考虑将大时间段拆分为多个小时间段,分批获取、分批处理、分批释放。
总结
在处理 Tick 级别的高频数据时,内存管理尤为重要。通过 disable_cache() + gc.collect() + 优化返回数据结构 的组合拳,基本可以彻底解决 get_ticks 导致的内存超限被杀进程的问题。