同样一份数据,换一种写法占的内存能差几千倍;省内存的本质是别一次把所有东西都摆出来。
前面几节都在讲硬件怎么配合,这一节把镜头收回来,看程序自己该怎么省。
一、省内存不是抠门
内存便宜了,但省内存仍然不是小题大做: 内存不够时会去交换——而交换是毫秒级的,一交换程序就卡。 缓存命中率高不高,取决于数据占多大——占得小,才装得进缓存。 手机和嵌入式设备的内存是硬约束——省下来的就是能不能跑起来。 所以省内存的目标不是【数字好看】,而是别让程序把时间耗在等数据上。
二、几条通用原则
一、别一次把所有东西摆出来。 数据是流水就用生成器,用到才造。 二、能用紧凑的表示就别用松散的。 同样 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 秒实验
- 用 sys.getsizeof 量一量你程序里最常见的那个容器有多大。
- 把一段用列表收集结果的代码改成生成器,看内存占用变化。
常见误区
- 以为省内存是过早优化。它直接决定缓存命中率和会不会触发交换。
- 凭感觉改。不动手量,改出来的多半是幻觉。
自测三问(点开对答案)
1. 省内存的核心思路是什么? —— 同一时刻内存里躺着的东西越少越好——按需生成、用紧凑表示、少复制、及时释放。 2. 列表和生成器在内存上差多少? —— 10 万个整数:列表约 80 万字节,生成器不到 200 字节,相差上万倍。 3. 优化内存前应该先做什么? —— 先测量:用工具看单个对象的大小、看总量的增长趋势,找出意外的大户。- ☐ 下一节(5.6 了解一下磁盘的物理结构)
相关笔记
- 程序加载到内存后才能运行 — 占得少,加载也快
- 了解一下磁盘的物理结构 — 为什么数据摆放方式这么要紧
- 用好内存先从数组开始 — 紧凑排列的好处
- ⌨️ 编程原理 · 索引 — 把编程全貌理一遍
- ⚙️ 程序是怎样跑起来的 · 索引