当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/313342.html

相关文章:

  • AI Agent 重塑产业发展新格局
  • 联想笔记本安装系统之后一直转圈圈的问题了?无法正常进入到系统配置界面,原来是BIOS中的VMD问题
  • Autoswagger:揭露隐藏 API 授权缺陷的开源工具
  • 使用CMake构建项目的完整指南
  • [LINUX操作系统]shell脚本之循环
  • 【Qt】QObject::startTimer: Timers cannot be started from another thread
  • 如何玩转 Kubernetes K8S
  • 【QT】概述
  • 快速搭建一个非生产k8s环境
  • Android 之 网络通信(HTTP/TCP/UDP/JSON)
  • 量子物理学的前沿意义虚无、形式混乱
  • 入门MicroPython+ESP32:ESP32链接WIFI全攻略
  • Dify中自定义工具类的类型
  • 多模态后训练反常识:长思维链SFT和RL的协同困境
  • GitLab 代码管理平台部署及使用
  • CCF-GESP 等级考试 2025年6月认证C++一级真题解析
  • 扩散模型(一)——综述
  • 介绍JAVA语言、介绍greenfoot 工具
  • 案例介绍|JSON数据格式的转换|pyecharts模块简介
  • noob靶机复现笔记
  • 纯JavaScript实现文本选择工具栏:功能详解与源码解析
  • RAG 知识库实战指南:基于 Spring AI 构建 AI 知识问答应用
  • Git用法记录
  • UE5的渲染Debug技巧
  • C语言字符串拷贝的三重境界:从下标到指针的华丽变身
  • 设备健康管理标准规范:技术架构与合规性实现指南
  • 《人形机器人的觉醒:技术革命与碳基未来》——类人关节设计:人工肌肉研发进展及一款超生物肌肉Hypermusclet的设计与制造
  • K8S服务发现原理及开发框架的配合
  • k8s黑马教程笔记
  • LeetCode 刷题【29. 两数相除】