首页 > 解决方案 > numpy如何避免gc引用计数对子进程的访问复制

问题描述

在 POSIX 系统上,在你 fork() 之后,数据只能在你写入子进程后复制到子进程(写时复制)。但是由于python将引用计数存储在对象头中,所以每次在子进程中迭代一个列表时,它都会将其复制到它的内存中。

使用列表和其他数据结构进行测试,我可以断言该行为,以及核心开发人员的一些证实: https ://github.com/python/cpython/pull/3705#issuecomment-420201071

但是在用 numpy 数组测试之后,这并没有发生。

import ctypes
import os

import numpy as np
import psutil


def sharing_with_numpy():
    ppid = os.getpid()
    print(f'\nSystem used memory: {int(psutil.virtual_memory().used / (1024 * 1024))} MB')
    big_data = np.array([[item, item] for item in list(range(10000000))])
    print(f'\nSystem used memory: {int(psutil.virtual_memory().used / (1024 * 1024))} MB')
    print(ctypes.c_long.from_address(id(big_data)).value)
    ref1 = big_data[0]
    ref2 = big_data[0]
    print(ctypes.c_long.from_address(id(big_data)).value)

    print(f'\nSystem used memory: {int(psutil.virtual_memory().used / (1024 * 1024))} MB')
    for i in range(5):
        if ppid == os.getpid():
            os.fork()
    for x in big_data:
        pass
    print(f'\nSystem used memory: {int(psutil.virtual_memory().used / (1024 * 1024))} MB')


if __name__ == "__main__":
    sharing_with_numpy()

输出:

System used memory: 163 MB # before array allocation
System used memory: 318 MB # after array allocation
1 # reference count of the array
3 # reference count of the array
System used memory: 318 MB # before fork()
System used memory: 324 MB # after fork() and loop to reference array
System used memory: 328 MB # after fork() and loop to reference array
System used memory: 329 MB # after fork() and loop to reference array
System used memory: 331 MB # after fork() and loop to reference array
System used memory: 340 MB # after fork() and loop to reference array
System used memory: 342 MB # after fork() and loop to reference array

如您所见,内存增加了,但只是略微增加,这表明整个数组没有被复制。

我一直试图了解没有运气发生了什么,你能解释一下吗?谢谢

标签: pythonnumpymemory-managementfork

解决方案


numpy数组有一个对象头,其中包含一个指向底层数据的指针,单独分配。数据本身没有任何引用计数,因此仅通过读取它不会被修改。

由于numpy数组是批量分配的,因此用于支持数据存储的较大分配不是来自对象标头来自的小对象池(它们通常是直接从操作系统批量分配的,通​​过mmap[*NIX] 或VirtualAlloc[Windows] ,而不是从在许多分配中细分的内存堆中分配的)。由于它们不与任何引用计数的页面共享页面(它们是原始 C 类型,而不是int具有自己的对象头的 Python 或类似类型),因此这些页面永远不会被写入,因此永远不会被复制。


推荐阅读