为什么我的循环在每次迭代时需要更多内存?

时间:2016-03-17 17:17:04

标签: 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()所需的内存,而我可以将结果保存在内存中。

2 个答案:

答案 0 :(得分:2)

虽然脚本确实使用了相当多的内存,即使使用"较小的"

的答案
  

每次并行时Python都会克隆我的整个环境   运行进程,包括f()不需要的变量?怎么样   我可以阻止这种行为吗?

它以某种方式 clone 环境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上,这是完全不同的,因为从.exe而不是分叉执行新的Python解释器。由于您提到使用htop,因此您使用了某种类似UNIX或UNIX的系统,并且获得了COW语义。

  

对于for循环的每次迭代,在simulation()中处理   内存使用量等于我的代码使用的总内存。

生成的进程将显示几乎相同的RSS值,但这可能会产生误导,因为如果不发生写操作,它们大多会占用映射到多个进程的相同实际物理内存。使用Pool.map时,故事会更复杂一些,因为它会将迭代器切换为多个块,并将其作为单独的任务提交给进程池"。此提交发生在IPC上,并且将复制提交的数据。在您的示例中,IPC和2 ** 20函数调用也支配CPU使用率。用simulation中的单个矢量化乘法替换映射使得该机器上的脚本运行时间从大约150s到 0.66s

我们可以通过(某种程度上)简化示例来观察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,因此它必须为新数组分配空间以容纳两者旧的结果和新的和免费的现在未使用的旧结果数组。

答案 1 :(得分:0)

也许你应该知道threadprocessOperating System之间的区别。见What is the difference between a process and a thread

在for循环中,有processes,而不是threads。线程共享创建它的进程的地址空间;进程有自己的地址空间。

您可以打印流程ID,输入os.getpid()