You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python中中间值内存处理机制的疑问及实测分析

Python中中间值内存处理机制的疑问及实测分析

最近我在研究Python里不同序列构建方式的内存占用时,遇到了一个和直觉不太一致的情况,想和大家聊聊。

比如,当我需要从两个列表构建一个新对象时,直觉上觉得先拼接列表再迭代,会因为生成了临时的拼接列表而占用更多内存;但如果用itertools.chain来逐个生成元素,应该能避免这个临时列表的内存开销。但实测下来,两者的内存占用居然完全一样,这就让我有点懵了——难道是我的测试方法有问题?

我写了一段测试代码来验证:

from itertools import pairwise, chain

from random import choice
from string import ascii_lowercase
import tracemalloc


def measure_allocation():
    s = [choice(ascii_lowercase) for _ in range(1_000_000)]
    
    tracemalloc.start()
    # 第一种方式:列表拼接后转元组
    res1 = tuple(['^']+s)
    snapshot1 = tracemalloc.take_snapshot()
    top_stats = snapshot1.statistics('lineno')
    for stat in top_stats[:10]:
        print(stat)

    tracemalloc.stop()
    print('*'*80)
    
    tracemalloc.start()
    # 第二种方式:用chain串联后转元组
    res2 = tuple(chain(['^'],s))
    snapshot2 = tracemalloc.take_snapshot()

    top_stats = snapshot2.statistics('lineno')
    for stat in top_stats[:10]:
        print(stat)
    
    tracemalloc.stop()

if __name__ == "__main__":
    import sys
    print(sys.version)
    print('*'*80)
    
    measure_allocation()

运行后的结果是:

3.12.6 (main, Sep  6 2024, 19:03:47) [GCC 13.3.0]
********************************************************************************
/tmp/tmp.MbQI2VsYCZ/foo.py:13: size=7813 KiB, count=1, average=7813 KiB
********************************************************************************
/tmp/tmp.MbQI2VsYCZ/foo.py:24: size=7813 KiB, count=1, average=7813 KiB

按我原来的理解,第一种方式(res1 = tuple(['^']+s))的执行流程应该是这样的:

  • 先计算内部的['^'] + s,生成一个全新的临时列表,这个列表的大小和s差不多(毕竟列表扩容是按块来的)
  • 这时候内存里同时存在原列表s和这个临时列表,内存占用应该是翻倍的
  • 然后再迭代这个临时列表来构建元组,之后临时列表才会被垃圾回收

但实测结果显示,两种方式的内存峰值居然完全一致,看起来好像只分配了元组的内存,完全没有临时列表的开销?这就让我很疑惑,到底是我的测试哪里错了,还是Python的内存处理机制和我想的不一样?

备注:内容来源于stack exchange,提问作者Mathias Sven

火山引擎 最新活动