You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么PyPy3在数据量超1000万时字典操作变慢,如何优化

现象原因
  1. PyPy默认采用分代垃圾回收策略,新创建的对象会首先放在名为nursery的新生代内存区域,默认大小通常为2GB。当900万条数据的总内存低于该阈值时,所有DATA列表中的字典对象都停留在nursery中,JIT编译器可以对字典访问、存在性判断操作做充分的逃逸分析和快路径优化,此时性能远高于CPython。
  2. 当数据量上升到1000万时,总内存占用超过nursery阈值,所有DATA列表的对象会被GC晋升到老年代。PyPy对老年代对象的访问需要增加额外的GC内存屏障开销,且JIT无法针对老年代的大量小字典对象做访问缓存优化,原本纳秒级的字典操作开销暴涨数倍,最终导致整体耗时陡增。
  3. 该场景下的性能瓶颈并非test函数中的目标字典d的操作(其最多仅存储10001个键,属于极小字典),而是循环过程中对DATA列表内每一个小字典的属性访问开销。
优化方案
  • 调整PyPy GC nursery阈值
    运行脚本前设置环境变量PYPY_GC_NURSERY,将nursery大小调至大于1000万条数据的总内存即可,例如:
    PYPY_GC_NURSERY=3G pypy3 test.py
    
    调整后1000万条数据也会全部存放在nursery中,性能可恢复到900万数据量时的水平。
  • 替换高开销的字典结构
    DATA列表中的元素无需使用字典存储,改用内存占用更低、访问更快的元组/命名元组存储,可将总内存占用降低60%以上,避免触发nursery阈值。示例生成代码:
    DATA = [(random.choice(ACTION), random.randint(0, RAND_SPREAD), random.choice(QUANTITY)) for x in range(TOTAL_NUM_DATA)]
    
    循环访问时直接按下标取值,完全消除字典访问开销。
  • 分批处理数据
    无需一次性生成全量数据存入内存,采用生成器分批生成、分批处理,单批数据量控制在100万以内,确保内存中始终只有少量存活对象,不会触发老年代晋升。
  • 简化循环逻辑
    可以用dict.get()方法合并键存在性判断和赋值操作,减少一次字典查询开销:
    d[key] = d.get(key, 0) + data['val']
    
    也可以直接使用collections.defaultdict(int)来存储计数,进一步简化逻辑。

内容的提问来源于stack exchange,提问作者user1179317

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 21:36:02