之前用Python写了个量化回测平台,最一开始直接用list储存行情信息,然后考虑到回测周期长了以后大量行情会比较占用内存,帅帅就从list继承出了一个FIFOList,指定一个长度,在超过这个长度后,每次append都会把第一个元素删掉,以保证内存占用保持在较小规模。一开始没觉得有什么,后来跑了一下,5分钟的K线,四五个月的行情回测花了十几秒,但并没有涉及特别复杂的算法。花了点时间排查,发现大量的时间都花在FIFOList上了,于是用Cython重写了一个List类,通过循环移动Cursor来实现,不涉及元素加入和删除的工作,用这个替换FIFOList以后回测相同的数据只用了2秒。虽然这个时间还是太长,但是改进是可观的。经过讨论认为这可能是Python语言的极限了。很惊讶List的操作占用的时间会如此夸张。Python的List,对其尾部元素的添加和删除操作占用的时间都是O(1),但是对首元素操作则要占用O(n),n是列表长度。因为要一次移动后面的元素。虽然知道这个,可是真的想不到如此简单常见的列表操作会成为性能瓶颈。

最近又学习用Haskell写回测,在计算完每一期的收益后就用++添加到列表的最后,最后形成一个收益序列。这个版本的程序要算半个小时(汗)。检查代码的时候发现这个梗,就改成了在列表首部添加,最后reverse,整个程序瞬间就计算完了。Haskell的列表结构是head:tail形式的,因此在首部添加是效率最高的,相反往尾部添加则需要复杂的展开和移动,尤其是数据量大了以后这个操作的耗时是不能忍受的。

一个微不足道的小问题,从来没当回事,却成为性能的瓶颈。在此记录,引以为戒。