如何提升Python循环处理千万级期权数据的运行性能?
脚本运行慢、随迭代次数增加逐步减速的根源,是pandas逐行向DataFrame追加数据的机制设计:
pandas的DataFrame基于连续内存块存储,每调用一次append或loc新增一行,需要重新申请内存、全量拷贝已有DataFrame内容。在1400万行的规模下,表的行数越多,单次拷贝的开销越大,因此会出现进度条速度逐步下降的现象。这个问题和循环本身、Cython优化没有关联,即使换用loc替代append,也无法从根本上解决性能问题。
不需要重写原有判断逻辑,仅调整结果存储方式,即可获得数百倍的性能提升:
- 循环过程中不再向pandas DataFrame写入数据,改用普通Python空列表存储每一行的计算结果
- Python列表的
append操作是固定时间复杂度的内存指针操作,不会随列表长度增加出现速度衰减 - 全量循环执行完成后,将存储结果的列表一次性转换为pandas DataFrame
示例改动代码:
# 移除原有循环前初始化空DataFrame的逻辑 # differences = pd.DataFrame(columns = treasury_maturities) # 初始化普通Python列表存储结果 result_rows = [] # 原有循环判断逻辑完全保留 for index, row in tqdm(options.iterrows()): # 原有if/elif/else分支逻辑全部保留 list_s = # 原有生成差值列表的代码 # 将结果append到普通Python列表,替代原有differences.append操作 result_rows.append(list_s) # 循环全部完成后,一次性转换为DataFrame differences = pd.DataFrame(result_rows, columns=treasury_maturities)
按这个方案改动,1400万行规模的循环总耗时可控制在10-20分钟,远低于原脚本数天的运行时长,且无额外学习成本。
原有判断语句存在逻辑漏洞:
# 该写法无法实现多日期判断,Python的or运算符会直接返回第一个真值,即仅判断2008-12-10的日期条件 elif (pd.to_datetime("2008-12-10") or pd.to_datetime("2008-12-18") or pd.to_datetime("2008-12-24")) == row.QuoteDate:
需替换为row.QuoteDate in [pd.to_datetime("2008-12-10"), pd.to_datetime("2008-12-18"), pd.to_datetime("2008-12-24")],即可实现三个日期的正确判断。
待基础方案跑通后,可进一步移除全量循环,采用numpy向量化计算:
- 将国债期限转换为行向量、期权到期时间转换为列向量,通过广播机制一次性完成所有行的期限差值计算,底层由C代码执行,毫秒级即可完成1400万行的矩阵运算
- 将原有判断规则转换为布尔掩码,批量替换无效值,无需逐行判断
该方案可将整体运行时长压缩至秒级,但需要对pandas/numpy的向量化操作有基本了解。
完全无需引入Cython。当前的性能瓶颈并非Python循环本身的执行速度,而是pandas逐行操作的内存拷贝开销。即使将循环用Cython重写,只要保留逐行append到DataFrame的逻辑,依然会出现逐步减速的问题。Cython适用于完全无法向量化、纯Python循环逻辑复杂的场景,对当前问题而言学习成本高、收益极低,属于过度优化。
内容的提问来源于stack exchange,提问作者Wasonic

