⚙️ 第五章 内存与磁盘的密切联系
📄 一句话概括

同样一份数据,换一种写法占的内存能差几千倍;省内存的本质是别一次把所有东西都摆出来。

📝 本节和前面几节的关系

前面几节都在讲硬件怎么配合,这一节把镜头收回来,看程序自己该怎么省。

一、省内存不是抠门

内存便宜了,但省内存仍然不是小题大做: 内存不够时会去交换——而交换是毫秒级的,一交换程序就卡。 缓存命中率高不高,取决于数据占多大——占得小,才装得进缓存。 手机和嵌入式设备的内存是硬约束——省下来的就是能不能跑起来。 所以省内存的目标不是【数字好看】,而是别让程序把时间耗在等数据上。

二、几条通用原则

一、别一次把所有东西摆出来。 数据是流水就用生成器,用到才造。 二、能用紧凑的表示就别用松散的。 同样 10 万个字符,按字节存比按字符存省得多。 三、别无意中复制。 传一份大对象时小心那些【顺手拷贝一下】的写法。 四、用完就放手。 长期挂着的大对象,比造得快更可怕。 四条其实是一句话:同一时刻,内存里躺着的东西越少越好。

三、落到写法上

列表换生成器:[...] 换成 (...),内存从几万字节掉到几百字节。 字符串换字节:只装 ASCII 时,bytes 每个字符 1 字节,str 要多不少。 自定义对象换定长数组:1000 个点用紧凑数组存坐标,比 1000 个对象省得多。 拼接换成一次性构造:循环里不断 s = s + x 会反复造新串,用 "".join 才是一次。

四、要拿数据说话

省内存最忌凭感觉,该用工具去看真实占用: 先看单个对象多大——Python 里 sys.getsizeof 就能报出来。 再看总量和增长——程序跑一段时间,内存是平的还是持续爬升。 重点找【意外的大户】——往往是某个缓存字典没清,或者一次读进了一整个大文件。 记住那句老话:先量,再改。 没有测量,优化出来的多半是幻觉。

五、动手 Demo:节约内存的编程技巧

同样的数据,换一种写法占的内存可能差几千倍。 先看最经典的对照:列表 对 生成器。

import sys
lst = [i for i in range(100000)]
gen = (i for i in range(100000))
rng = range(100000)
print("list ", sys.getsizeof(lst))
print("生成器", sys.getsizeof(gen))
print("range", sys.getsizeof(rng))

实跑输出:

--------------------------------------------------------------------
节约内存的编程技巧
--------------------------------------------------------------------

同样的数据,换一种写法占的内存可能差几千倍。

  先看一个最经典的对照——**列表 对 生成器**:

  写法                           占用字节     说明
  ------------------------------------------------------------------
  [i for i in range(100000)]     800984       10 万个整数全都摆出来
  (i for i in range(100000))     192          只记住【算到哪儿了】
  range(100000)                  48           连算都不用,按需现算

  再看基础类型各占多少字节:

  值      类型      占几字节
  ---------------------------
  0       int       28
  0.0     float     24
  ''      str       41
  b''     bytes     33
  ()      tuple     40

  最后看容器【本身】的开销(同样放 10 万个东西):

  容器      内容            占用字节
  -----------------------------------
  bytes     10 万个字节     100033
  str       10 万个字符     100041
  list      10 万个整数     800056
  set       10 万个整数     4194520
  dict      10 万组键值     5242960

  三条最实用的:
    **能用生成器就别用列表**——数据是流水的时候,没必要全摆出来
    **能用 bytes 就别用 str**——同样 10 万个字符,一个只要 1 字节
    **要紧凑就用定长数组**——按类型排好,比一个元素一个对象省得多

10 万个整数:列表 800984 字节,生成器 192 字节,range 48 字节——差了一万六千倍。 列表把 10 万个整数全摆出来;生成器只记住【算到哪儿了】;range 连算都不用。

30 秒实验

  1. 用 sys.getsizeof 量一量你程序里最常见的那个容器有多大。
  2. 把一段用列表收集结果的代码改成生成器,看内存占用变化。

常见误区

  1. 以为省内存是过早优化。它直接决定缓存命中率和会不会触发交换。
  2. 凭感觉改。不动手量,改出来的多半是幻觉。
自测三问(点开对答案) 1. 省内存的核心思路是什么? —— 同一时刻内存里躺着的东西越少越好——按需生成、用紧凑表示、少复制、及时释放。 2. 列表和生成器在内存上差多少? —— 10 万个整数:列表约 80 万字节,生成器不到 200 字节,相差上万倍。 3. 优化内存前应该先做什么? —— 先测量:用工具看单个对象的大小、看总量的增长趋势,找出意外的大户。

相关笔记