Python 为什么我的循环每次迭代都需要更多内存?
我试图降低python 3代码的内存需求。现在,for循环的每次迭代都需要比上一次更多的内存 我编写了一小段代码,其行为与我的项目相同: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
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...