如何使用子进程强制Python释放内存?
我在研究Python内存管理,想要减少我应用程序的内存占用。有人建议使用子进程来解决这个问题,但我对该怎么做有些困惑。能不能给我一个简单的例子,说明如何把这个...
def my_function():
x = range(1000000)
y = copy.deepcopy(x)
del x
return y
@subprocess_witchcraft
def my_function_dispatcher(*args):
return my_function()
...变成一个真正的子进程函数,而且不需要额外的“空闲列表”?
附加问题:
这个“空闲列表”的概念在Python的C扩展中也适用吗?
2 个回答
这个被接受的答案是用Python 2写的。下面是Python 3版本的代码:
# To run this, save it to a file that looks like a valid Python module, e.g.
# "foo.py" - multiprocessing requires being able to import the main module.
# Then run it with "python foo.py".
import multiprocessing, random, sys, os, time
def create_list(size):
# utility function for clarity - runs in subprocess
maxint = 2**63-1 #sys.maxint #
randrange = random.randrange
return [randrange(maxint) for i in range(size)]
def run_test(state):
# this function is run in a separate process
size = state['list_size']
print('creating a list with %d random elements - this can take a while... ' % size,)
sys.stdout.flush()
lst = create_list(size)
print('done')
t0 = time.time()
lst.sort()
t1 = time.time()
state['time'] = t1 - t0
if __name__ == '__main__':
manager = multiprocessing.Manager()
state = manager.dict(list_size=5*1000*1000) # shared state
p = multiprocessing.Process(target=run_test, args=(state,))
p.start()
p.join()
print('time to sort: %.3f' % state['time'])
print('my PID is %d, sleeping for a minute...' % os.getpid())
time.sleep(60)
# at this point you can inspect the running process to see that it
# does not consume excess memory
关于优化建议,最重要的是确保 my_function()
只在一个子进程中被调用。至于 deepcopy
和 del
这些其实没什么关系——一旦你在一个进程中创建了五百万个不同的整数,同时持有它们,基本上就完蛋了。即使你停止引用这些对象,Python 也会通过保持对五百万个空整数对象的引用,来释放它们,这些对象会处于一种等待状态,准备给下一个想要创建五百万个整数的函数使用。这就是其他回答中提到的 空闲列表,它可以让整数和浮点数的分配和释放变得非常快速。值得一提的是,这并不是内存泄漏,因为内存确实是可以用于后续的分配。但是,这块内存在进程结束之前不会被返回给系统,也不会被用于分配其他类型的对象。
大多数程序并不会遇到这个问题,因为大部分程序不会创建异常庞大的数字列表,然后释放它们,再期望能将这块内存用于其他对象。使用 numpy
的程序也很安全,因为 numpy
将其数组中的数字数据以紧凑的本地格式存储。对于那些确实遵循这种使用模式的程序,解决问题的方法是尽量不要在同一时间创建大量整数,至少不要在需要将内存返回给系统的进程中创建。虽然不清楚你具体的使用场景是什么,但现实中的解决方案可能需要的不仅仅是一个“魔法装饰器”。
这就是子进程的用武之地:如果数字列表是在另一个进程中创建的,那么与该列表相关的所有内存,包括但不限于整数的存储,都会在子进程终止时被释放并返回给系统。当然,你需要设计程序,使得列表可以在子系统中创建和处理,而不需要转移所有这些数字。子进程可以接收创建数据集所需的信息,并可以将处理列表后获得的信息发送回来。
为了说明这个原则,我们来升级你的例子,让整个列表确实需要存在——比如我们在基准测试排序算法。我们想创建一个巨大的整数列表,对其进行排序,并可靠地释放与该列表相关的内存,以便下一个基准测试可以为自己的需求分配内存,而不必担心内存不足。为了生成子进程并进行通信,可以使用 multiprocessing
模块:
# To run this, save it to a file that looks like a valid Python module, e.g.
# "foo.py" - multiprocessing requires being able to import the main module.
# Then run it with "python foo.py".
import multiprocessing, random, sys, os, time
def create_list(size):
# utility function for clarity - runs in subprocess
maxint = sys.maxint
randrange = random.randrange
return [randrange(maxint) for i in xrange(size)]
def run_test(state):
# this function is run in a separate process
size = state['list_size']
print 'creating a list with %d random elements - this can take a while... ' % size,
sys.stdout.flush()
lst = create_list(size)
print 'done'
t0 = time.time()
lst.sort()
t1 = time.time()
state['time'] = t1 - t0
if __name__ == '__main__':
manager = multiprocessing.Manager()
state = manager.dict(list_size=5*1000*1000) # shared state
p = multiprocessing.Process(target=run_test, args=(state,))
p.start()
p.join()
print 'time to sort: %.3f' % state['time']
print 'my PID is %d, sleeping for a minute...' % os.getpid()
time.sleep(60)
# at this point you can inspect the running process to see that it
# does not consume excess memory
附加回答
对于附加问题,很难给出答案,因为问题不够清晰。“空闲列表概念”就是一个概念,是一种需要在常规 Python 分配器之上明确编码的实现策略。大多数 Python 类型并不使用这种分配策略,比如通过 class
语句创建的类实例就不使用。实现一个空闲列表并不难,但这相对高级,通常没有充分理由不会去做。如果某个扩展作者选择为其某种类型使用空闲列表,可以预期他们会意识到空闲列表所带来的权衡——以额外快速的分配/释放为代价,换取一些额外的空间(用于空闲列表中的对象和空闲列表本身),并且无法将内存用于其他用途。