簡體   English   中英

Memory 的使用隨着 Python 的 multiprocessing.pool 不斷增長

[英]Memory usage keep growing with Python's multiprocessing.pool

這是程序:

#!/usr/bin/python

import multiprocessing

def dummy_func(r):
    pass

def worker():
    pass

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=16)
    for index in range(0,100000):
        pool.apply_async(worker, callback=dummy_func)

    # clean up
    pool.close()
    pool.join()

我發現 memory 的使用(VIRT 和 RES)一直增長到 close()/join(),有什么解決方案可以擺脫這個嗎? 我用 2.7 嘗試了 maxtasksperchild,但它也沒有幫助。

我有一個更復雜的程序,它調用 apply_async() ~6M 次,在 ~1.5M 點我已經獲得了 6G+ RES,為了避免所有其他因素,我將程序簡化為上述版本。

編輯:

原來這個版本效果更好,感謝大家的意見:

#!/usr/bin/python

import multiprocessing

ready_list = []
def dummy_func(index):
    global ready_list
    ready_list.append(index)

def worker(index):
    return index

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=16)
    result = {}
    for index in range(0,1000000):
        result[index] = (pool.apply_async(worker, (index,), callback=dummy_func))
        for ready in ready_list:
            result[ready].wait()
            del result[ready]
        ready_list = []

    # clean up
    pool.close()
    pool.join()

我沒有放任何鎖,因為我相信主進程是單線程的(回調或多或少像我閱讀的每個文檔的事件驅動的東西)。

我將 v1 的索引范圍更改為 1,000,000,與 v2 相同並進行了一些測試 - 對我來說很奇怪 v2 甚至比 v1(33 秒對 37 秒)快約 10%,也許 v1 做了太多內部列表維護工作。 v2 絕對是 memory 使用率的贏家,它從未超過 300M (VIRT) 和 50M (RES),而 v1 曾經是 370M/120M,最好是 330M/85M。 所有數字只是3~4次測試,僅供參考。

我最近遇到了內存問題,因為我多次使用多處理函數,所以它不斷產生進程,並將它們留在內存中。

這是我現在使用的解決方案:

def myParallelProcess(ahugearray):
    from multiprocessing import Pool
    from contextlib import closing
    with closing(Pool(15)) as p:
        res = p.imap_unordered(simple_matching, ahugearray, 100)
    return res

只需在循環中創建池並在循環結束時使用pool.close()將其pool.close()

使用map_async而不是apply_async以避免過多的內存使用。

對於第一個示例,更改以下兩行:

for index in range(0,100000):
    pool.apply_async(worker, callback=dummy_func)

pool.map_async(worker, range(100000), callback=dummy_func)

在您可以在top看到其內存使用情況之前,它會在眨眼間完成。 將列表更改為更大的列表以查看差異。 但是請注意,如果map_async沒有__len__方法,它會首先將您傳遞給它的可迭代對象轉換為列表以計算其長度。 如果您有一個包含大量元素的迭代器,您可以使用itertools.islice以較小的塊處理它們。

我在具有更多數據的實際程序中遇到了內存問題,最終發現罪魁禍首是apply_async

PS,在內存使用方面,您的兩個示例沒有明顯區別。

我正在處理一個非常大的 3d 點雲數據集。 我嘗試使用多處理模塊來加速處理,但我開始出現內存不足錯誤。 經過一些研究和測試,我確定我填充要處理的任務隊列的速度比子進程清空它的速度要快得多。 我確定通過分塊,或使用 map_async 或其他我可以調整負載的東西,但我不想對周圍的邏輯進行重大更改。

我遇到的愚蠢解決方案是間歇性地檢查pool._cache長度,如果緩存太大,則等待隊列清空。

在我的主循環中,我已經有了一個計數器和一個狀態代碼:

# Update status
count += 1
if count%10000 == 0:
    sys.stdout.write('.')
    if len(pool._cache) > 1e6:
        print "waiting for cache to clear..."
        last.wait() # Where last is assigned the latest ApplyResult

因此,每向池中插入 10k 次,我就會檢查是否有超過 100 萬個操作排隊(主進程中使用了大約 1G 的內存)。 當隊列已滿時,我只等待最后插入的作業完成。

現在我的程序可以運行幾個小時而不會耗盡內存。 當工作人員繼續處理數據時,主進程只是偶爾暫停。

順便說一句,_cache 成員記錄在多處理模塊池示例中:

#
# Check there are no outstanding tasks
#

assert not pool._cache, 'cache = %r' % pool._cache

我認為這類似於我發布的問題,但我不確定您是否有相同的延遲。 我的問題是我從多處理池中產生結果的速度比我消耗它們的速度快,所以它們在內存中建立起來。 為了避免這種情況,我使用了一個信號量來限制輸入到池中,這樣它們就不會比我消耗的輸出領先太多。

您可以限制每個子進程的任務數

multiprocessing.Pool(maxtasksperchild=1)

maxtasksperchild是工作進程在退出並被新的工作進程替換之前可以完成的任務數,以釋放未使用的資源。 默認的 maxtasksperchild 是 None,這意味着工作進程將與池一樣長。 關聯

我不得不結合使用maxtasksperchildchunksize讓事情最終得到控制。 很難說一般情況,因為數據可能會有很大差異。

對於我的情況,我有:

  • 文件從 1-11GB 不等,具有 20,000 到 150,000 個特征,可單獨處理並插入到 MongoDB 集合中。 問題主要發生在大文件上。
  • 只需提供實例上可用進程數的一半:
    • memory 將被完全使用,可能與某種 memory 損失有關,因為每個子進程的任務太多,一切最終都會掛起
    • 或者進程將大部分處於休眠狀態,因為塊大小太大而某些進程剛剛結束了所有繁重的數據。 因此,睡眠進程只會無緣無故地用完 memory,事情最終也會掛起。

對我有用的是這樣的(必須根據您的數據調整參數):

with Pool(processes=num_processes, maxtasksperchild=10) as pool:
    results = pool.starmap(
        process_feature,
        [(idx, feature) for idx, feature in enumerate(features)],
        chunksize=100,
    )

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM