我正在使用IPython.parallel来处理群集上的大量数据。我运行的远程功能如下:
def evalPoint(point, theta):
# do some complex calculation
return (cost, grad)
由此函数调用:
def eval(theta, client, lview, data):
async_results = []
for point in data:
# evaluate current data point
ar = lview.apply_async(evalPoint, point, theta)
async_results.append(ar)
# wait for all results to come back
client.wait(async_results)
# and retrieve their values
values = [ar.get() for ar in async_results]
# unzip data from original tuple
totalCost, totalGrad = zip(*values)
avgGrad = np.mean(totalGrad, axis=0)
avgCost = np.mean(totalCost, axis=0)
return (avgCost, avgGrad)
如果我运行代码:
client = Client(profile="ssh")
client[:].execute("import numpy as np")
lview = client.load_balanced_view()
for i in xrange(100):
eval(theta, client, lview, data)
内存使用量持续增长,直到我最终耗尽(76GB内存)。我简化了evalPoint
什么也不做,以确保它不是罪魁祸首。
eval
的第一部分是从IPython的文档中复制的,关于如何使用负载均衡器。第二部分(解压缩和平均)是相当直接的,所以我认为这不会导致内存泄漏。此外,我尝试手动删除eval
中的对象并致电gc.collect()
,但没有运气。
我希望有IPython.parallel经验的人可以指出一些明显我做错的事情,或者能够确认这实际上是内存泄漏。
其他一些事实:
%run
有关,我相信在这个版本的IPython中已经修复了(同样,我没有使用%run
) 更新
此外,我尝试将记录从内存切换到SQLiteDB,以防出现问题,但仍然存在同样的问题。
响应(1)
内存消耗肯定在控制器中(我可以通过以下方式验证:(a)在另一台机器上运行客户端,以及(b)观察顶部)。我没有意识到非SQLiteDB仍会消耗内存,所以我没有打扰清除。
如果我使用DictDB并清除,我仍然会看到内存消耗增加,但速度要慢得多。 20次调用eval()时,它徘徊在2GB左右。
如果我使用MongoDB和purge,看起来mongod需要大约4.5GB的内存和ipcluster大约2.5GB。
如果我使用SQLite并尝试清除,则会收到以下错误:
File "/usr/local/lib/python2.7/dist-packages/IPython/parallel/controller/hub.py", line 1076, in purge_results
self.db.drop_matching_records(dict(completed={'$ne':None}))
File "/usr/local/lib/python2.7/dist-packages/IPython/parallel/controller/sqlitedb.py", line 359, in drop_matching_records
expr,args = self._render_expression(check)
File "/usr/local/lib/python2.7/dist-packages/IPython/parallel/controller/sqlitedb.py", line 296, in _render_expression
expr = "%s %s"%null_operators[op]
TypeError: not enough arguments for format string
所以,我想如果我使用DictDB,我可能会好的(我今晚要试试)。我不确定是否仍然存在一些内存消耗(我也像你建议的那样在客户端中清除)。
答案 0 :(得分:7)
是控制器进程正在增长,还是客户端,或者两者兼而有之?
控制器会记住所有请求和所有结果,因此将此信息存储在简单dict中的默认行为将导致不断增长。使用数据库后端(sqlite或最好是mongodb,如果可用)应解决此问题,或者client.purge_results()
方法可用于指示控制器丢弃任何/所有结果历史记录(如果您将删除它们,则将从数据库中删除它们)正在使用一个。)
客户端本身将其所有结果缓存在其results
字典中,因此这也将导致随着时间的推移而增长。不幸的是,这个有点难以处理,因为引用可以在各种方向传播,并且不受控制器的db后端的影响。
这是IPython中的known issue,但是现在,您应该能够通过删除客户端结果/元数据中的条目来手动清除引用,如果您的视图一直存在,它就有自己的结果dict:
# ...
# and retrieve their values
values = [ar.get() for ar in async_results]
# clear references to the local cache of results:
for ar in async_results:
for msg_id in ar.msg_ids:
del lview.results[msg_id]
del client.results[msg_id]
del client.metadata[msg_id]
或者,您可以使用简单的dict.clear()
清除整个客户端缓存:
view.results.clear()
client.results.clear()
client.metadata.clear()
旁注:
视图有自己的wait()方法,因此您根本不需要将客户端传递给您的函数。应该可以通过View访问所有内容,如果您确实需要客户端(例如,用于清除缓存),则可以将其设置为view.client
。