python 多处理特有的内存管理
python multiprocessing peculiar memory management
我有一个简单的多处理代码:
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
是传递给 pool.map 的长度 numprocs
的列表,所以我希望 numprocs
进程被分叉并且 data
的每个元素被传递给其中一个那些。
我测试了 3 个不同的 data
对象:data1
和 data2
具有几乎相同的大小,但是当使用 data1
时,每个进程都会获得同一对象的副本,而当使用data2
,一个进程获得全部 data1
,而其他进程仅获得一个“1”(基本上什么都没有)。 data3
基本是空的,用来衡量fork进程的基本开销成本。
问题:
data1
和 data2
之间使用的整体内存有很大不同。我测量最后一行 (pool.map()) 使用的额外内存量,我得到:
data1
:~8GB
data2
:~0.8GB
data3
: ~0GB
1) 和 2) 不应该相等,因为传递给子级的数据总量是相同的。这是怎么回事?
我在 Linux 机器上从 /proc/meminfo 的 Active
字段测量内存使用情况(总计 - MemFree
给出相同的答案)
您看到的是 pool.map
的一个副作用,它必须 pickle data
中的每个元素才能将其传递给您的子进程。
泡菜的时候发现data1
比data2
大很多。这是一个小脚本,可以将每个列表中每个元素的总 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))
输出:
data1: 2002003470
data2: 200202593
data3: 480
可以看到,data1
的pickled总大小是data2
的十倍左右,正好符合两者内存占用的数量级差异。
之所以 data2
泡菜这么小,是因为 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
,所有浅拷贝都在可迭代对象 (data2[0]
) 的一个顶级元素内。因此,当 pool.map
对该元素进行 pickle 时,它可以将所有浅表副本删除到一个单独的子列表中,然后只 pickle 那个子列表,以及描述该子列表如何嵌套到 data2
。对于 data1
,浅拷贝跨越 data1
的不同顶层元素,因此 pool.map
单独腌制它们,这意味着重复数据删除丢失了。所以,当你 unpickle data1
时,浅表副本消失了,每个元素都有一个唯一但相等的子列表副本。
比较这两个示例,其中 data1
和 data2
与您的列表相同:
>>> 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
,由于浅拷贝而进行的所有重复数据删除都丢失了,这使得内存使用率更高。
我有一个简单的多处理代码:
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
是传递给 pool.map 的长度 numprocs
的列表,所以我希望 numprocs
进程被分叉并且 data
的每个元素被传递给其中一个那些。
我测试了 3 个不同的 data
对象:data1
和 data2
具有几乎相同的大小,但是当使用 data1
时,每个进程都会获得同一对象的副本,而当使用data2
,一个进程获得全部 data1
,而其他进程仅获得一个“1”(基本上什么都没有)。 data3
基本是空的,用来衡量fork进程的基本开销成本。
问题:
data1
和 data2
之间使用的整体内存有很大不同。我测量最后一行 (pool.map()) 使用的额外内存量,我得到:
data1
:~8GBdata2
:~0.8GBdata3
: ~0GB
1) 和 2) 不应该相等,因为传递给子级的数据总量是相同的。这是怎么回事?
我在 Linux 机器上从 /proc/meminfo 的 Active
字段测量内存使用情况(总计 - MemFree
给出相同的答案)
您看到的是 pool.map
的一个副作用,它必须 pickle data
中的每个元素才能将其传递给您的子进程。
泡菜的时候发现data1
比data2
大很多。这是一个小脚本,可以将每个列表中每个元素的总 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))
输出:
data1: 2002003470
data2: 200202593
data3: 480
可以看到,data1
的pickled总大小是data2
的十倍左右,正好符合两者内存占用的数量级差异。
之所以 data2
泡菜这么小,是因为 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
,所有浅拷贝都在可迭代对象 (data2[0]
) 的一个顶级元素内。因此,当 pool.map
对该元素进行 pickle 时,它可以将所有浅表副本删除到一个单独的子列表中,然后只 pickle 那个子列表,以及描述该子列表如何嵌套到 data2
。对于 data1
,浅拷贝跨越 data1
的不同顶层元素,因此 pool.map
单独腌制它们,这意味着重复数据删除丢失了。所以,当你 unpickle data1
时,浅表副本消失了,每个元素都有一个唯一但相等的子列表副本。
比较这两个示例,其中 data1
和 data2
与您的列表相同:
>>> 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
,由于浅拷贝而进行的所有重复数据删除都丢失了,这使得内存使用率更高。