python---python中的内存分配
文章目录
- 1、基于值的内存管理模式
- 2、元组和列表的内存分配机制
- 2.1、List的内存分配
- 列表内存分配的基本特点
- 扩容机制
- 缩容机制
- 内存布局
- 内存分配优化策略
- 内存使用示例
- 与元组的比较
- 元组总结
- 2.2、列表的copy和切片
- 2.3、Tuple的内存分配
- 小元组的缓存机制
- 空元组的特殊处理
- 内存占用比较
- 内存分配优化策略
- 相关文章推荐
1、基于值的内存管理模式
Python采用基于值的内存管理模式,相同的值在内存中只有一份。
首先明确一点,整数、实数、字符串是真正意义上的值。
a = 1
b = 1
print(id(a))
print(id(b))
print(id(1))a = 2.0
b = 2.0
print(id(a))
print(id(b))
print(id(2.0))a = 'c'
b = 'c'
print(id(a))
print(id(b))
print(id('c'))
2、元组和列表的内存分配机制
List:动态数组,元素可变,可改变大小(append,pop 等)
Tuple:静态数组,不可变,数据一旦创建后不可改变
2.1、List的内存分配
List的内存是动态的,当List中的元素数据是N时,List的内存大小大于N+1,第一个元素存储列表长度,和列表的元信息。其内存分配机制比元组更复杂,因为需要支持动态扩容和缩容操作。
动态分配的过程并不会一个个的分配空间。并且在动态分配的过程中,列表的首地址不改变。
测试代码:
import sys
list, num =[] , 0
while num <= 100 :length = len(list)size = sys.getsizeof(list)print('length = '+str(length)+' size = '+str(size) + ' 列表地址:' + str(id(list)))list.append(1)num += 1
测试结果:
从测试结果可以看出,扩容的过程中,列表首地址不改变。
length = 0 size = 28 列表地址:18766920
length = 1 size = 44 列表地址:18766920
length = 2 size = 44 列表地址:18766920
length = 3 size = 44 列表地址:18766920
length = 4 size = 44 列表地址:18766920
length = 5 size = 60 列表地址:18766920
length = 6 size = 60 列表地址:18766920
length = 7 size = 60 列表地址:18766920
length = 8 size = 60 列表地址:18766920
length = 9 size = 92 列表地址:18766920
length = 10 size = 92 列表地址:18766920
length = 11 size = 92 列表地址:18766920
length = 12 size = 92 列表地址:18766920
length = 13 size = 92 列表地址:18766920
length = 14 size = 92 列表地址:18766920
length = 15 size = 92 列表地址:18766920
length = 16 size = 92 列表地址:18766920
length = 17 size = 124 列表地址:18766920
length = 18 size = 124 列表地址:18766920
length = 19 size = 124 列表地址:18766920
length = 20 size = 124 列表地址:18766920
length = 21 size = 124 列表地址:18766920
length = 22 size = 124 列表地址:18766920
length = 23 size = 124 列表地址:18766920
length = 24 size = 124 列表地址:18766920
length = 25 size = 156 列表地址:18766920
length = 26 size = 156 列表地址:18766920
length = 27 size = 156 列表地址:18766920
length = 28 size = 156 列表地址:18766920
length = 29 size = 156 列表地址:18766920
length = 30 size = 156 列表地址:18766920
length = 31 size = 156 列表地址:18766920
length = 32 size = 156 列表地址:18766920
length = 33 size = 188 列表地址:18766920
length = 34 size = 188 列表地址:18766920
length = 35 size = 188 列表地址:18766920
length = 36 size = 188 列表地址:18766920
length = 37 size = 188 列表地址:18766920
length = 38 size = 188 列表地址:18766920
length = 39 size = 188 列表地址:18766920
length = 40 size = 188 列表地址:18766920
length = 41 size = 236 列表地址:18766920
length = 42 size = 236 列表地址:18766920
length = 43 size = 236 列表地址:18766920
length = 44 size = 236 列表地址:18766920
length = 45 size = 236 列表地址:18766920
length = 46 size = 236 列表地址:18766920
length = 47 size = 236 列表地址:18766920
length = 48 size = 236 列表地址:18766920
length = 49 size = 236 列表地址:18766920
length = 50 size = 236 列表地址:18766920
length = 51 size = 236 列表地址:18766920
length = 52 size = 236 列表地址:18766920
length = 53 size = 284 列表地址:18766920
length = 54 size = 284 列表地址:18766920
length = 55 size = 284 列表地址:18766920
length = 56 size = 284 列表地址:18766920
length = 57 size = 284 列表地址:18766920
length = 58 size = 284 列表地址:18766920
length = 59 size = 284 列表地址:18766920
length = 60 size = 284 列表地址:18766920
length = 61 size = 284 列表地址:18766920
length = 62 size = 284 列表地址:18766920
length = 63 size = 284 列表地址:18766920
length = 64 size = 284 列表地址:18766920
length = 65 size = 332 列表地址:18766920
length = 66 size = 332 列表地址:18766920
length = 67 size = 332 列表地址:18766920
length = 68 size = 332 列表地址:18766920
length = 69 size = 332 列表地址:18766920
length = 70 size = 332 列表地址:18766920
length = 71 size = 332 列表地址:18766920
length = 72 size = 332 列表地址:18766920
length = 73 size = 332 列表地址:18766920
length = 74 size = 332 列表地址:18766920
length = 75 size = 332 列表地址:18766920
length = 76 size = 332 列表地址:18766920
length = 77 size = 396 列表地址:18766920
length = 78 size = 396 列表地址:18766920
length = 79 size = 396 列表地址:18766920
length = 80 size = 396 列表地址:18766920
length = 81 size = 396 列表地址:18766920
length = 82 size = 396 列表地址:18766920
length = 83 size = 396 列表地址:18766920
length = 84 size = 396 列表地址:18766920
length = 85 size = 396 列表地址:18766920
length = 86 size = 396 列表地址:18766920
length = 87 size = 396 列表地址:18766920
length = 88 size = 396 列表地址:18766920
length = 89 size = 396 列表地址:18766920
length = 90 size = 396 列表地址:18766920
length = 91 size = 396 列表地址:18766920
length = 92 size = 396 列表地址:18766920
length = 93 size = 460 列表地址:18766920
length = 94 size = 460 列表地址:18766920
length = 95 size = 460 列表地址:18766920
length = 96 size = 460 列表地址:18766920
length = 97 size = 460 列表地址:18766920
length = 98 size = 460 列表地址:18766920
length = 99 size = 460 列表地址:18766920
length = 100 size = 460 列表地址:18766920
列表内存分配的基本特点
1、动态数组实现:Python列表在底层实现上是动态数组(类似于C++的vector),而不是链表。
2、超额分配(Over-allocation):列表分配的内存通常比实际需要的多,以减少频繁扩容的开销。
3、自动扩容/缩容:当元素数量超过或低于某些阈值时,列表会自动调整其内存容量。
扩容机制
当列表需要扩容时,Python会按照特定策略分配更大的内存空间:
测试代码:
import sys
lst = []
for i in range(10):print(f"长度: {len(lst)}, 容量: {sys.getsizeof(lst)} 字节")lst.append(i)
测试结果:
长度: 0, 容量: 28 字节
长度: 1, 容量: 44 字节
长度: 2, 容量: 44 字节
长度: 3, 容量: 44 字节
长度: 4, 容量: 44 字节
长度: 5, 容量: 60 字节
长度: 6, 容量: 60 字节
长度: 7, 容量: 60 字节
长度: 8, 容量: 60 字节
长度: 9, 容量: 92 字节
典型的扩容策略是:
1、新分配的大小 = 当前大小 + (当前大小 >> 3) + (当前大小 < 9 ? 3 : 6)
2、这种策略实现了约12.5%的超额分配,平衡了内存使用和性能
缩容机制
列表不会在元素移除时立即缩小内存占用,但某些操作会触发缩容:
测试代码:
# 缩容机制
lst = [i for i in range(1000)]
print(sys.getsizeof(lst)) # 较大值
lst.clear()
print(sys.getsizeof(lst)) # 显著减小
lst = lst[:10] # 切片操作可能不会立即缩容
测试结果:
4428
28
内存布局
列表对象在内存中存储:
1、列表头信息(引用计数、类型指针等)
2、元素数量(ob_size)
3、分配的总容量
4、指向元素数组的指针
5、实际存储元素的数组(存储的是对象的引用,而非对象本身)
内存分配优化策略
1、提前分配:创建大型列表时,预分配空间比动态追加更高效:
# 较差的方式 - 多次扩容
lst = []
for i in range(10000):lst.append(i)# 更好的方式 - 预分配
lst = [0] * 10000
for i in range(10000):lst[i] = i
2、列表推导式优化:列表推导式通常比循环追加更高效:
# 更高效
lst = [i for i in range(10000)]
3、避免频繁缩容:Python不会立即缩小列表内存,频繁改变大小会影响性能。
内存使用示例
windows系统如果提示<ModuleNotFoundError: No module named ‘pympler’>的错误,在cmd中使用命令:pip install pympler ,等待其自动下载。
测试代码:
import sys
from pympler import asizeof# 创建列表
lst = [i for i in range(1000)]# 查看内存占用
print(f"列表大小 (sys): {sys.getsizeof(lst)} 字节") # 仅列表结构
print(f"列表大小 (pympler): {asizeof.asizeof(lst)} 字节") # 包括元素# 扩容测试
import time
start = time.time()
lst = []
for i in range(1000000):lst.append(i)
print(f"动态追加耗时: {time.time()-start:.3f}秒")start = time.time()
lst = [0] * 1000000
for i in range(1000000):lst[i] = i
print(f"预分配耗时: {time.time()-start:.3f}秒")
运行结果:
列表大小 (sys): 4428 字节
列表大小 (pympler): 20432 字节
动态追加耗时: 0.059秒
预分配耗时: 0.052秒
与元组的比较
1、内存占用:列表比相同元素的元组占用更多内存(约多出16-24字节用于存储容量等信息)
2、扩容成本:列表追加元素可能触发昂贵的扩容操作
3、内存碎片:频繁修改列表可能导致内存碎片
元组总结
1、列表使用动态数组实现,支持高效随机访问
2、采用超额分配策略减少频繁扩容的开销
3、扩容时大约增加12.5%的额外空间
4、不会立即缩小内存占用,避免频繁内存操作
5、预分配和列表推导式可以优化性能
6、对于固定大小的序列,考虑使用元组或array.array节省内存
2.2、列表的copy和切片
copy和分片才会新建的列表地址(引用则不会):
# 列表引用,本质上是给被引用的列表取别名
a = [1, 2, 3]
b = a
print(id(a), id(b), (id(a) == id(b)))
# a和b本质是同一个列表,对a操作会改变b
a.append(4)
print(a, b)# copy和分片
a = [1, 2, 3]
b = a.copy()
c = a[:]
print(id(a), id(b), id(c), (id(a) == id(b)), (id(a) == id(c)))
a.append(4)
print(a, b, c)
测试结果:
34235176 34235176 True
[1, 2, 3, 4] [1, 2, 3, 4]
34234472 34235240 34235176 False False
[1, 2, 3, 4] [1, 2, 3] [1, 2, 3]
2.3、Tuple的内存分配
Tuple不支持 改变,但是可以粘贴两个元祖组成一个新的元组,这个操作类似于List 的 append,但是又不会额外的分配内存。每次都会进行一此新分配内存和内存copy操作。
小元组的缓存机制
Python会缓存一定数量的小元组(空元组或小尺寸元组):
a = (1, 2, 3)
b = (1, 2, 3)
print(a is b) # 可能输出True,因为小元组被缓存复用
空元组的特殊处理
空元组在Python中是单例对象:
a = ()
b = ()
print(a is b) # 总是输出True
内存占用比较
元组通常比列表占用更少的内存:
因为列表通常需要预留一些空间。
import sys
lst = [1, 2, 3]
tup = (1, 2, 3)
print(sys.getsizeof(lst)) # 通常比元组大
print(sys.getsizeof(tup))
内存分配优化策略
1、元组重用:解释器会重用不可变的元组对象,特别是在函数调用和返回时。
2、固定大小:由于不可变,元组在创建时就确定了大小,不需要像列表那样预留扩展空间。
3、快速分配:元组的内存分配通常比列表更快,因为不需要考虑后续的扩容操作。
相关文章推荐
相关文章1