社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

解析 Python 内存管理的秘密

新语数据故事汇 • 7 月前 • 192 次点击  

Python因其灵活性和动态性而广受赞誉,但同时也因其内存占用较大而备受诟病。为了实现这种灵活性和动态性,Python在内存方面做出了一些牺牲。在这里,我们不会深入解释Python的机制,因为这过于复杂。相反,我们将简要展示几个关于内存消耗的示例,比如:一个整型数值占用多少内存?列表又是如何占用内存的?为什么一个整型数值需要28个字节的内存?通过了解这些基本示例,我们可以为今后的程序开发和内存优化提供有益的建议。

如何测量变量的内存使用情况?

最简单的方法当然是使用Python内置方法。也就是说,在sys模块中,有一个名为getsizeof()的函数。当我们想要检查变量的大小时,使用它非常方便。

import sys
x = [1, 2, 3, 4, 5]print("Size of list:", sys.getsizeof(x), "bytes")for item in x: print("Size of list item:", sys.getsizeof(item), "bytes")

上述代码中的列表x的大小是120,而每个项的整数值是28个字节。为啥加起来和与列表大小不一致?

为什么列表的大小不等于其所有项大小的总和?

getsizeof()函数只测量对象本身,而不是它所引用或包含的对象。因此,104个字节只表示列表的“容器”大小。换句话说,它不包括其中的项。容器还包括指针,这些指针是维护对象列表的所有“开销”。

这120 个字节甚至不包括列表中的数值元素。列表的总大小应该是120 + 28*5 = 260字节。换句话说,当我们使用sys.getsizeof()函数来获取对象的内存大小时,如果对象间接引用了其他对象,它不会给出完整的大小。

为什么在Python中一个整数需要28个字节?

我们看下面的整数示例:

import sysn = 1size = sys.getsizeof(n)print(size)

一个简单的整型占用28个字节包括以下组件/字段

  • Reference count (8字节)

  • Type pointer (8字节)

  • Size (8字节)

  • Integer value(4字节)

以上所有“组件/字段对于任何Python对象都存在的内存开销,并导致一个简单整数占用28字节。

Reference Count

“引用计数”(“Reference Count”)是Python用于垃圾回收的。也就是说,每当对象被某个东西引用时,它的引用计数就会加1。当引用计数变为0时,Python的垃圾回收过程会释放内存。这意味着当前系统进程无法使用该对象,因为没有对它的引用。

import sys
a = [1, 2, 3] b = a c = a
print('sys.getrefcount(a):',sys.getrefcount(a))
del bprint('sys.getrefcount(a):',sys.getrefcount(a))
del cprint('sys.getrefcount(a):',sys.getrefcount(a)) del a

在上述代码中,我们创建了一个列表并将其赋给变量a。然后,我们定义了两个其他变量b和c来引用它。通过sys.getrefcount()函数检查变量a的引用计数,应该是3。由于该函数在尝试访问引用计数时会创建一个临时引用,因此输出的引用计数会变成4。

接着,我们删除了变量b,变量a的引用计数减少了1。再删除变量c后,变量a的引用计数再次减少了1。最后,我们删除了变量a。实际上,此时它的引用计数应该是0。但由于我们的程序已经删除了对它的所有引用,因此无法再访问它的引用。变量a的引用计数为0,垃圾回收机制会意识到可以释放变量a分配的内存。

Type Pointer

与大多数使用静态类型的编程语言(例如C/C++和Java)不同,Python以其动态类型而闻名。基本上,Python允许对象在运行时灵活地更改其类型。请看下面的代码。

# Initially, the variable is an integermy_var = 1print(f"my_var is an integer: {my_var}, type: {type(my_var)}")
# Change type to stringmy_var = "Hello, world!"print(f"my_var is now a string: '{my_var}', type: {type(my_var)}")
# Change type to listmy_var = [1, 2, 3, 4, 5]print(f"my_var is now a list: {my_var}, type: {type(my_var)}")
# Change type to a dictionarymy_var = {"key": "value"}print(f"my_var is now a dictionary: {my_var}, type: {type(my_var)}")
# Change type to a functiondef my_function(): return "I am a function"
my_var = my_functionprint(f"my_var is now a function: {my_var}, type: {type(my_var)}")

在上面的代码中,我们多次重新定义了变量my_var,并且每次类型都不同。在Python中,这完全没问题。然而,在大多数其他编程语言中,比如Java,我们通常必须用固定类型定义一个变量。
因此,为了拥抱这种灵活性,Python必须使用另外8个字节来存储这个“类型指针”,以确保对象具有可以在运行时更改的动态类型。换句话说,这个“类型指针”可以指向其他类型。

Size Fields

同样,这也是因为Python的动态特性。Python中许多类型变量的精度和长度都是动态的。因此,Python需要这个“开销”来存储这些信息。

这个“大小字段”将以整数形式表示精度。由于Python中的任意精度,变量的“大小”可能需要适应该变量的增长,以支持更大的值或长度。

Integer Value

这28个字节必须包括数字本身。默认情况下,Python为整数值分配了4个字节。在64位系统中,我相信大多数人都在使用,这意味着我们可以拥有的数字介于-2³⁰+1和2³⁰-1之间。请看下面的示例。

n = 2**30-1size = sys.getsizeof(n)print(f"Size of the integer {n}: {size} bytes",type(n))
n = 2**30size = sys.getsizeof(n)print(f"Size of the integer {n}: {size} bytes",type(n))

在上面的示例中,当我们尝试使用数字 2³⁰ 时,对象的总大小变为了 32 字节。这是因为 4 字节不足以存储这个整数值,因此 Python 给它另外分配了 4 字节。


以上示例简单示例展示了Python内存机制,解释了为何简单的整型需要28字节的内存。了解了基本对象的内存结构,说明了为实现Python的灵活性和动态特性而引入的复杂对象内存结构,需要牺牲内存。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/170445
 
192 次点击