最初我注意到这个问题,当我使用一个巨大的DataFrame时,试图应用于str。将()降低到字符串功能,它花费了我10 GB的内存。
我决定用一个简单的例子来研究这个问题,这就是我发现的:
首先检查一个进程占用了多少内存:
import sys
import psutil
from guppy import hpy
import gc
def print_in_mb(n):
print(n // (1024 * 1024), 'Mb')
def print_object_size(obj):
print_in_mb(sys.getsizeof(obj))
print_in_mb(psutil.Process().memory_info().rss)
64 Mb
然后用字符串创建一个相当大的元组:
a = tuple('HeLlO, WoRlD' for _ in range(10_000_000))
print_object_size(a)
76 Mb
print_in_mb(psutil.Process().memory_info().rss)
142 Mb
然后我想将每个字符串转换为小写:
a = tuple(x.lower() for x in a)
print_object_size(a)
76 Mb
print_in_mb(psutil.Process().memory_info().rss)
764 Mb
Boom!在应用.lower()
方法后,程序需要764 Mb的内存,这肯定超过了存储创建的对象所需的内存量。
调用gc.collect()
没有帮助:
gc.collect()
print_in_mb(psutil.Process().memory_info().rss)
764 Mb
为什么调用.lower()/.upper()
方法会占用这么多内存?有什么方法可以避免这样的额外内存分配吗?
PS此问题仅在处理结构时出现。如果我只创建一个大字符串,然后降低它-没有额外的内存分配。
x = 'Aa' * 100_000_000
print_object_size(x)
190 Mb
print_in_mb(psutil.Process().memory_info().rss)
955 Mb
y = x.lower()
print_object_size(x)
190 Mb
print_in_mb(psutil.Process().memory_info().rss)
1145 Mb - OK!
1条答案
按热度按时间wydwbb8l1#
您只查看了元组的大小,而忘记了添加所有字符串的大小。一种方法是:
对于原始的字符串元组,它显示了
0 Mb
,因为所有字符串都是同一个对象。因为字符串在源代码中,所以Python将其内部化。对于降低的字符串元组,它显示了
581 Mb
,因为降低会创建新的单独的字符串对象。您可以通过对字符串进行插入来减少内存使用:
为此,它再次向我显示了
0 Mb
,因为它现在再次只是一个反复重复的字符串对象。为什么你看到764 - 142 = 622 MB的增长,而不仅仅是581 MB?这主要是因为对象与可被16整除的地址在内存中对齐。因此,虽然字符串的数据(包括内容和元数据)只有61个字节(
sys.getsizeof(a[0])
报告),但实际上需要64个字节,其中3个没有使用。而64*10^7 / 2^20是610 Mb,接近你的622 Mb。(Btw我建议正确使用MB,i.即除以1 e6。而不是计算MiB并将其称为“Mb”。这将使数字更加清晰。例如,1000万个字符串占用64字节,每个字符串占用640 MB。)