python多处理特殊内存管理

2024-06-02 06:30:53 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个简单的多处理代码:

from multiprocessing import Pool
import time 

def worker(data):
    time.sleep(20)

if __name__ == "__main__":
    numprocs = 10
    pool = Pool(numprocs)
    a = ['a' for i in xrange(1000000)]
    b = [a+[] for i in xrange(100)]

    data1 = [b+[] for i in range(numprocs)]
    data2 = [data1+[]] + ['1' for i in range(numprocs-1)]
    data3 = [['1'] for i in range(numprocs)]

    #data = data1
    #data = data2
    data = data3


    result = pool.map(worker,data)

b只是一个大列表。data是传递给的长度numprocs的列表池.map,所以我希望numprocs进程被分叉,data的每个元素都被传递给其中一个。 我测试了3个不同的data对象:data1和{}的大小几乎相同,但是当使用data1时,每个进程都会得到同一个对象的副本,而当使用data2时,一个进程得到所有的{},而其他进程只得到一个“1”(基本上什么都没有)。data3基本上是空的,用于度量分叉进程的基本开销成本。在

问题:data1和{}之间,使用的总内存差别很大。我测量最后一行使用的额外内存量(池.map())我得到:

  1. data1:~8GB
  2. data2:~0.8GB
  3. data3:~0GB

不应该1)和2)相等,因为传递给子对象的数据总量是相同的。怎么回事?在

我从Linux机器上的/proc/meminfo的Active字段测量内存使用情况(Total-MemFree给出了相同的答案)


Tags: 对象内存inimportmapfordatatime
1条回答
网友
1楼 · 发布于 2024-06-02 06:30:53

您看到的是pool.map必须对data中的每个元素进行pickle以将其传递给子进程的副作用。在

当你腌制它时,data1data2大得多。下面是一个小脚本,它将每个列表中每个元素的总pickle大小相加:

import pickle
import sys
from collections import OrderedDict

numprocs = 10
a = ['a' for i in range(1000000)]
b = [a+[] for i in range(100)]

data1 = [b+[] for i in range(numprocs)]
data2 = [data1+[]] + ['1' for i in range(numprocs-1)]
data3 = [['1'] for i in range(numprocs)]
sizes = OrderedDict()
for idx, data in enumerate((data1, data2, data3)):
    sizes['data{}'.format(idx+1)] = sum(sys.getsizeof(pickle.dumps(d))
                                            for d in data)

for k, v in sizes.items():
    print("{}: {}".format(k, v))

输出:

^{pr2}$

如您所见,data1的总pickle大小大约是data2大小的十倍,这与两者在内存使用上的数量级差异完全匹配。在

之所以data2pickles这么小是因为Cilyan在评论中提到的;实际上,当您创建每个data列表时,实际上是在进行浅拷贝:

>>> data2[0][2][0] is data2[0][0][0]
True
>>> data2[0][2][0] is data2[0][3][0]
True
>>> data2[0][2][0] is data2[0][4][0]
True

现在,data1也在制作浅拷贝:

>>> data1[0][0] is data1[1][0]
True
>>> data1[0][0] is data1[2][0]
True

关键的区别在于使用data2,所有的浅拷贝都在iterable(data2[0])的一个顶层元素中。因此,当pool.mappickle该元素时,它可以将所有浅表副本反复制到一个单独的子列表中,只需pickle该子列表,以及描述该子列表如何嵌套到data2中的元数据。使用data1时,浅层副本跨data1的不同顶层元素,因此pool.map分别对它们进行pickle,这意味着重复数据消除丢失。因此,当您取消拾取data1时,浅层副本就不复存在了,每个元素都有一个唯一但相同的子列表副本。在

比较这两个例子,其中data1和{}与您的列表相同:

>>> before_pickle1 = data1[0]
>>> before_pickle2 = data1[1]
>>> before_pickle1 is before_pickle2
False
>>> before_pickle1[0] is before_pickle2[0]
True  # The lists are the same before pickling
>>> after_pickle1 = pickle.loads(pickle.dumps(before_pickle1))
>>> after_pickle2 = pickle.loads(pickle.dumps(before_pickle2))
>>> after_pickle1[0] is after_pickle2[0]
False  # After pickling, the lists are not the same
>>> before_pickle1 = data2[0]

>>> before_pickle1[0][0] is before_pickle1[1][0]
True
>>> after_pickle1 = pickle.loads(pickle.dumps(before_pickle1))
>>> after_pickle1[0][0] is after_pickle1[1][0]
True # The lists are still the same after pickling

这模拟了pool.map所做的相同的酸洗。使用data1,由于浅拷贝而进行的所有重复数据消除都将丢失,这使得内存使用率更高。在

相关问题 更多 >