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

Python 为什么我的循环每次迭代都需要更多内存?,python,memory,python-multiprocessing,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 = Poo

我试图降低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()所需的内存,而我可以将结果保存在内存中。

尽管脚本使用了相当多的内存,即使使用了“较小”的示例值,但

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

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

COW
似乎可用并正在使用,但在其他系统上可能不是这样。在Windows上,这是完全不同的,因为新的Python解释器是从
.exe
执行的,而不是分叉。因为您提到使用
htop
,所以您使用的是某种UNIX或类UNIX系统的风格,您得到了
COW
语义

对于循环的每次迭代,For simulation()中的每个进程 内存使用量等于代码使用的总内存

生成的进程将显示几乎相同的值,但这可能会产生误导,因为如果不发生写入,它们通常会占用映射到多个进程的相同实际物理内存。这个故事有点复杂,因为它“将iterable分割成若干块,作为单独的任务提交给进程池”。此提交将重复进行,提交的数据将被复制。在您的示例中,
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()
此机器上的输出:

现在,如果我们将函数
read\u 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)
结果完全不同:



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

也许您应该知道
操作系统中
线程
进程
之间的区别。看这个

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


您可以打印进程id,键入
os.getpid()

是,它克隆了程序的整个上下文。您应该使用
来保护这些主操作,如果uuu name\uuuuu=='uu main\uuuu':
至少。@snakecharmerb results[0][0]只是一个浮点数。@Ilja是的,说得不错。我在我的真实代码中这样做,但我认为对于这个例子来说,它不是必需的。我会插入它(但它不会改变行为)。@Isea在你编辑后,我可以观察到你描述的行为。它不像我指出的那样黑白分明。进程可以共享物理内存,尽管它们有自己的地址空间。
 % 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...
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...