python - 为什么我的循环每次迭代都需要更多内存?

标签 python memory python-multiprocessing

我正在尝试减少我的 python 3 代码的内存需求。现在,for 循环的每次迭代都需要比上一次更多的内存。

我写了一小段与我的项目具有相同行为的代码:

import numpy as np
from multiprocessing import Pool
from itertools import repeat


def simulation(steps, y):  # the function that starts the parallel execution of f()
    pool = Pool(processes=8, maxtasksperchild=int(steps/8))
    results = pool.starmap(f, zip(range(steps), repeat(y)), chunksize=int(steps/8))
    pool.close()
    return results


def f(steps, y):  # steps is used as a counter. My code doesn't need it.
        a, b = np.random.random(2)
        return y*a, y*b

def main():
    steps = 2**20  # amount of times a random sample is taken
    y = np.ones(5)  # dummy variable to show that the next iteration of the code depends on the previous one
    total_results = np.zeros((0,2))
    for i in range(5):
        results = simulation(steps, y[i-1])
        y[i] = results[0][0]
        total_results = np.vstack((total_results, results))

    print(total_results, y)

if __name__ == "__main__":
    main()

对于 for 循环的每次迭代,simulation() 中的每个线程的内存使用量都等于我的代码使用的总内存量。

每次运行并行进程时,Python 是否会克隆我的整个环境,包括 f() 不需要的变量?如何防止这种行为?

理想情况下,我希望我的代码只复制执行 f() 所需的内存,同时我可以将结果保存在内存中。

最佳答案

虽然脚本确实使用了相当多的内存,即使是“较小”的示例值,但答案是

Does Python clone my entire environment each time the parallel processes are run, including the variables not required by f()? How can I prevent this behaviour?

是它以某种方式克隆环境与forking一个新进程,但如果 copy-on-write语义是可用的,在写入之前不需要复制实际的物理内存。例如在这个系统上

 % uname -a 
Linux mypc 4.2.0-27-generic #32-Ubuntu SMP Fri Jan 22 04:49:08 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

COW 似乎可用并正在使用中,但在其他系统上可能并非如此。在 Windows 上,这是完全不同的,因为新的 Python 解释器是从 .exe 执行的,而不是 fork 。由于您提到使用 htop,因此您使用的是某种 UNIX 或类似 UNIX 的系统,并且您会获得 COW 语义。

For each iteration of the for loop the processes in simulation() each have a memory usage equal to the total memory used by my code.

生成的进程将显示几乎相同的 RSS 值,但这可能会产生误导,因为如果不发生写入,它们大多占用映射到多个进程的相同实际物理内存。与 Pool.map这个故事有点复杂,因为它“将可迭代对象分割成许多 block ,作为单独的任务提交给进程池”。此提交发生在 IPC并且提交的数据将被复制。在您的示例中, IPC 和 2**20 函数调用也支配 CPU 使用率。在 simulation 中用单个向量化乘法替换映射使脚本在这台机器上的运行时间从大约 150 秒缩短到 0.66 秒

我们可以观察 COW 的一个(有点)简化的例子,它分配一个大数组并将它传递给一个衍生的进程进行只读处理:

import numpy as np
from multiprocessing import Process, Condition, Event
from time import sleep
import psutil


def read_arr(arr, done, stop):
    with done:
        S = np.sum(arr)
        print(S)
        done.notify()
    while not stop.is_set(): 
        sleep(1)


def main():
    # Create a large array
    print('Available before A (MiB):', psutil.virtual_memory().available / 1024 ** 2)
    input("Press Enter...")
    A = np.random.random(2**28)
    print('Available before Process (MiB):', psutil.virtual_memory().available / 1024 ** 2)
    input("Press Enter...")
    done = Condition()
    stop = Event()
    p = Process(target=read_arr, args=(A, done, stop))
    with done:
        p.start()
        done.wait()
    print('Available with Process (MiB):', psutil.virtual_memory().available / 1024 ** 2)
    input("Press Enter...")
    stop.set()
    p.join()

if __name__ == '__main__':
    main()

这台机器上的输出:

 % python3 test.py
Available before A (MiB): 7779.25
Press Enter...
Available before Process (MiB): 5726.125
Press Enter...
134221579.355
Available with Process (MiB): 5720.79296875
Press Enter...

现在,如果我们将函数 read_arr 替换为修改数组的函数:

def mutate_arr(arr, done, stop):
    with done:
        arr[::4096] = 1
        S = np.sum(arr)
        print(S)
        done.notify()
    while not stop.is_set(): 
        sleep(1)

结果完全不同:

Available before A (MiB): 7626.12109375
Press Enter...
Available before Process (MiB): 5571.82421875
Press Enter...
134247509.654
Available with Process (MiB): 3518.453125
Press Enter...

for 循环在每次迭代后确实需要更多内存,但这很明显:它从映射中堆叠 total_results,因此它必须为新数组分配空间来保存旧数组结果和新的并释放现在未使用的旧结果数组。

关于python - 为什么我的循环每次迭代都需要更多内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36067453/

相关文章:

python - 从电话号码字符串中删除不需要的字符

python - App Engine(python)如何跨请求管理内存(超出软私有(private)内存限制)

c - 结构的内存对齐

python - 如何将 HDF5 数据映射到多个 Python 进程?

Python 多处理输出

python - 更改表单集中表单的显示顺序

python - 交换numpy数组中的列?

java - 在一行中为多个变量分配多个值

c - 从C中的物理内存地址读取结构

Python 多处理冲突添加到列表