优化建议的重要一点是确保my_function()
仅在子流程中调用。这deepcopy
and del
无关紧要——一旦你在一个进程中创建了五百万个不同的整数,同时保留所有这些整数,游戏就结束了。即使您停止引用这些对象,Python 也会通过保留对 500 万个空整数对象大小字段的引用来释放它们,这些字段等待下一个想要创建 500 万个整数的函数重用。这是空闲列表在另一个答案中提到,它购买了整数和浮点数的快速分配和释放。对于 Python 来说,公平地说,这不是内存泄漏,因为内存肯定可用于进一步分配。但是,在进程结束之前,该内存不会返回到系统,也不会被重用于除了分配相同类型的数字之外的任何其他用途。
大多数程序不存在这个问题,因为大多数程序不会创建病态的巨大数字列表,释放它们,然后期望将该内存重用于其他对象。程序使用numpy
也安全,因为numpy
以紧密封装的本机格式存储其数组的数值数据。对于遵循这种使用模式的程序,缓解问题的方法是首先不要同时创建大量整数,至少不要在需要将内存返回给系统的进程中。目前尚不清楚您的具体用例是什么,但现实世界的解决方案可能需要的不仅仅是“魔术装饰器”。
这就是子进程的用武之地:如果数字列表是在另一个进程中创建的,则与该列表关联的所有内存(包括但不限于整数的存储)都会被释放并通过终止行为返回到系统子流程。当然,您必须设计程序,以便可以在子系统中创建和处理列表,而不需要传输所有这些数字。子进程可以接收创建数据集所需的信息,并且可以发回通过处理列表获得的信息。
为了说明原理,让我们升级您的示例,以便整个列表实际上需要存在 - 假设我们正在对排序算法进行基准测试。我们想要创建一个巨大的整数列表,对其进行排序,并可靠地释放与该列表关联的内存,以便下一个基准测试可以根据自己的需要分配内存,而不必担心 RAM 耗尽。为了生成子进程并进行通信,这使用了multiprocessing
module:
# 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 类型都这样做not使用该分配策略,例如,它不用于使用创建的类的实例class
陈述。实现空闲列表并不难,但它是相当高级的,并且很少在没有充分理由的情况下进行。如果某个扩展作者has选择对其类型之一使用空闲列表,可以预期他们意识到空闲列表提供的权衡 - 以一些额外空间为代价获得超快的分配/释放(对于空闲列表上的对象)以及空闲列表本身)并且无法将内存重用于其他用途。