多处理:为什么在复制列表时与子进程共享一个 numpy 数组?
Multiprocessing: why is a numpy array shared with the child processes, while a list is copied?
我用这个script(见最后的代码)来评估当父进程分叉时全局对象是否被共享或复制。
简而言之,脚本创建了一个全局 data
对象,子进程遍历 data
。该脚本还监视内存使用情况,以评估对象是否被复制到子进程中。
结果如下:
data = np.ones((N,N))
。子进程中的操作:
data.sum()
。结果:data
是 shared(无副本)
data = list(range(pow(10, 8)))
。子进程中的操作:sum(data)
。结果:data
被 复制。
data = list(range(pow(10, 8)))
。子进程中的操作:for x in data: pass
。结果:data
被 复制。
由于写时复制,结果 1) 是预期的。我对结果 2) 和 3) 有点困惑。为什么 data
被复制了?
脚本
source
import multiprocessing as mp
import numpy as np
import logging
import os
logger = mp.log_to_stderr(logging.WARNING)
def free_memory():
total = 0
with open('/proc/meminfo', 'r') as f:
for line in f:
line = line.strip()
if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
field, amount, unit = line.split()
amount = int(amount)
if unit != 'kB':
raise ValueError(
'Unknown unit {u!r} in /proc/meminfo'.format(u = unit))
total += amount
return total
def worker(i):
x = data.sum() # Exercise access to data
logger.warn('Free memory: {m}'.format(m = free_memory()))
def main():
procs = [mp.Process(target = worker, args = (i, )) for i in range(4)]
for proc in procs:
proc.start()
for proc in procs:
proc.join()
logger.warn('Initial free: {m}'.format(m = free_memory()))
N = 15000
data = np.ones((N,N))
logger.warn('After allocating data: {m}'.format(m = free_memory()))
if __name__ == '__main__':
main()
详细结果
运行 1 输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 23.3 GB
[WARNING/Process-2] Free memory: 23.3 GB
[WARNING/Process-4] Free memory: 23.3 GB
[WARNING/Process-1] Free memory: 23.3 GB
[WARNING/Process-3] Free memory: 23.3 GB
运行 2 输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 21.9 GB
[WARNING/Process-2] Free memory: 12.6 GB
[WARNING/Process-4] Free memory: 12.7 GB
[WARNING/Process-1] Free memory: 16.3 GB
[WARNING/Process-3] Free memory: 17.1 GB
运行 3输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 21.9 GB
[WARNING/Process-2] Free memory: 12.6 GB
[WARNING/Process-4] Free memory: 13.1 GB
[WARNING/Process-1] Free memory: 14.6 GB
[WARNING/Process-3] Free memory: 19.3 GB
它们都是写时复制。你缺少的是当你这样做时,例如,
for x in data:
pass
data
中包含的每个对象的引用计数临时递增 1,一次一个,因为 x
依次绑定到每个对象。对于 int
对象,CPython 中的引用计数是基本对象布局的一部分,因此对象被复制(你 did 改变它,因为引用计数改变了)。
为了让事情更类似于 numpy.ones
的情况,尝试,例如,
data = [1] * 10**8
那么只有一个唯一对象被列表引用了很多次(10**8
),所以几乎没有什么可以复制的(同一个对象的引用计数多次递增和递减)。
我用这个script(见最后的代码)来评估当父进程分叉时全局对象是否被共享或复制。
简而言之,脚本创建了一个全局 data
对象,子进程遍历 data
。该脚本还监视内存使用情况,以评估对象是否被复制到子进程中。
结果如下:
data = np.ones((N,N))
。子进程中的操作:data.sum()
。结果:data
是 shared(无副本)data = list(range(pow(10, 8)))
。子进程中的操作:sum(data)
。结果:data
被 复制。data = list(range(pow(10, 8)))
。子进程中的操作:for x in data: pass
。结果:data
被 复制。
由于写时复制,结果 1) 是预期的。我对结果 2) 和 3) 有点困惑。为什么 data
被复制了?
脚本
source
import multiprocessing as mp
import numpy as np
import logging
import os
logger = mp.log_to_stderr(logging.WARNING)
def free_memory():
total = 0
with open('/proc/meminfo', 'r') as f:
for line in f:
line = line.strip()
if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
field, amount, unit = line.split()
amount = int(amount)
if unit != 'kB':
raise ValueError(
'Unknown unit {u!r} in /proc/meminfo'.format(u = unit))
total += amount
return total
def worker(i):
x = data.sum() # Exercise access to data
logger.warn('Free memory: {m}'.format(m = free_memory()))
def main():
procs = [mp.Process(target = worker, args = (i, )) for i in range(4)]
for proc in procs:
proc.start()
for proc in procs:
proc.join()
logger.warn('Initial free: {m}'.format(m = free_memory()))
N = 15000
data = np.ones((N,N))
logger.warn('After allocating data: {m}'.format(m = free_memory()))
if __name__ == '__main__':
main()
详细结果
运行 1 输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 23.3 GB
[WARNING/Process-2] Free memory: 23.3 GB
[WARNING/Process-4] Free memory: 23.3 GB
[WARNING/Process-1] Free memory: 23.3 GB
[WARNING/Process-3] Free memory: 23.3 GB
运行 2 输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 21.9 GB
[WARNING/Process-2] Free memory: 12.6 GB
[WARNING/Process-4] Free memory: 12.7 GB
[WARNING/Process-1] Free memory: 16.3 GB
[WARNING/Process-3] Free memory: 17.1 GB
运行 3输出
[WARNING/MainProcess] Initial free: 25.1 GB
[WARNING/MainProcess] After allocating data: 21.9 GB
[WARNING/Process-2] Free memory: 12.6 GB
[WARNING/Process-4] Free memory: 13.1 GB
[WARNING/Process-1] Free memory: 14.6 GB
[WARNING/Process-3] Free memory: 19.3 GB
它们都是写时复制。你缺少的是当你这样做时,例如,
for x in data:
pass
data
中包含的每个对象的引用计数临时递增 1,一次一个,因为 x
依次绑定到每个对象。对于 int
对象,CPython 中的引用计数是基本对象布局的一部分,因此对象被复制(你 did 改变它,因为引用计数改变了)。
为了让事情更类似于 numpy.ones
的情况,尝试,例如,
data = [1] * 10**8
那么只有一个唯一对象被列表引用了很多次(10**8
),所以几乎没有什么可以复制的(同一个对象的引用计数多次递增和递减)。