Python str.lower()导致内存泄漏

omjgkv6w  于 2023-05-02  发布在  Python
关注(0)|答案(1)|浏览(147)

最初我注意到这个问题,当我使用一个巨大的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!
wydwbb8l

wydwbb8l1#

您只查看了元组的大小,而忘记了添加所有字符串的大小。一种方法是:

objs = dict(zip(map(id, a), a))
print_in_mb(sum(map(sys.getsizeof, objs.values())))

对于原始的字符串元组,它显示了0 Mb,因为所有字符串都是同一个对象。因为字符串在源代码中,所以Python将其内部化。
对于降低的字符串元组,它显示了581 Mb,因为降低会创建新的单独的字符串对象。
您可以通过对字符串进行插入来减少内存使用:

a = tuple(sys.intern(x.lower()) for x in a)

为此,它再次向我显示了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。)

相关问题