Py学习  »  Python

如何销毁Python对象并释放内存

Thalish Sajeed • 5 年前 • 3261 次点击  

不幸的是,由于RAM的限制,我不得不将图像分成20000个块,并在将结果保存到磁盘之前对它们执行操作。

下面编写的代码应该在开始循环以处理接下来的20000个图像之前保存20000个图像的结果数据帧。

然而,这似乎并不能解决我的问题,因为在第一个for循环结束时,内存没有从RAM中释放出来

因此,在处理第50000条记录时,程序由于内存不足错误而崩溃。

在将对象保存到磁盘并调用垃圾收集器之后,我尝试删除它们,但是RAM的使用似乎没有下降。

我错过了什么?

#file_list_1 contains 100,000 images
file_list_chunks = list(divide_chunks(file_list_1,20000))
for count,f in enumerate(file_list_chunks):
    # make the Pool of workers
    pool = ThreadPool(64) 
    results = pool.map(get_image_features,f)
    # close the pool and wait for the work to finish 
    list_a, list_b = zip(*results)
    df = pd.DataFrame({'filename':list_a,'image_features':list_b})
    df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")
    del list_a
    del list_b
    del df
    gc.collect()
    pool.close() 
    pool.join()
    print("pool closed")
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/55357
 
3261 次点击  
文章 [ 8 ]  |  最新文章 5 年前
imposeren
Reply   •   1 楼
imposeren    6 年前

pd.DataFrame(...) 可能会在某些linux版本上泄漏(请参阅github issue and "workaround" ),所以即使 del df 可能没用。

pd.DataFrame.__del__ :

from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None


if no libc:
    print("Sorry, but pandas.DataFrame may leak over time even if it's instances are deleted...")


CHUNK_SIZE = 20000


#file_list_1 contains 100,000 images
with ThreadPool(64) as pool:
    for count,f in enumerate(divide_chunks(file_list_1, CHUNK_SIZE)):
        # make the Pool of workers
        results = pool.map(get_image_features,f)
        # close the pool and wait for the work to finish 
        list_a, list_b = zip(*results)
        df = pd.DataFrame({'filename':list_a,'image_features':list_b})
        df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")

        del df

        # 2 new lines of code:
        if libc:  # Fix leaking of pd.DataFrame(...)
            libc.malloc_trim(0)

print("pool closed")

P、 如果任何一个数据帧太大,这个解决方案将没有帮助。只有通过减少 CHUNK_SIZE

S.V
Reply   •   2 楼
S.V    6 年前

joblib 因为它允许并行化甚至是本地创建的函数(这是“实现的细节”,所以最好避免在模块中使它们成为全局的)。我的另一个建议是:不要在python中使用线程(和线程池),而是使用进程(和进程池)-这几乎总是一个更好的主意!只要确保在joblib中创建一个至少包含2个进程的池,否则它将在原始python进程中运行所有进程,因此RAM最终不会被释放。一旦joblib工作进程自动关闭,操作系统将完全释放它们分配的RAM。我最喜欢的武器是 joblib.Parallel . 如果需要将大数据(即大于2GB)传输给workers,请使用 joblib.dump (将python对象写入主进程中的文件)和 joblib.load

关于 del object :在python中,命令实际上并不删除对象。它只会减少它的参考计数器。当你跑的时候 import gc; gc.collect() 在python中释放内存的唯一100%可靠的方法是运行在并行进程中分配内存的代码,然后终止进程

MartinP
Reply   •   3 楼
MartinP    6 年前

我想有可能 celery

处理图像似乎是等幂的和原子的,所以它可以是 celery task .

你可以跑 a few workers 这将处理任务-使用图像。

configuration 内存泄漏。

user397836
Reply   •   4 楼
user397836    6 年前

简而言之,您不能在Python解释器中释放内存。最好的办法是使用多处理,因为每个进程都可以自己处理内存。

垃圾收集器将“释放”内存,但不在您可能期望的上下文中。页面和池的处理可以在CPython源代码中探索。这里还有一篇高级文章: https://realpython.com/python-memory-management/

Reddy Kilowatt
Reply   •   5 楼
Reddy Kilowatt    6 年前

不调用list(),它正在创建内存中的 从divide_chunks()返回的内容的列表。 这就是你的记忆问题可能发生的地方。

你不需要一次把所有的数据都记录下来。

请张贴堆栈跟踪,以便我们有更多信息

delica
Reply   •   6 楼
delica    6 年前

您的问题是,您正在使用应该使用多处理的线程(CPU绑定与IO绑定)。

我会像这样重构你的代码:

from multiprocessing import Pool

if __name__ == '__main__':
    cpus = multiprocessing.cpu_count()        
    with Pool(cpus-1) as p:
        p.map(get_image_features, file_list_1)

get_image_features 把这两行加在它的末尾。我不知道你到底是如何处理这些图像的,但我的想法是在每个进程中处理每个图像,然后立即将其保存到磁盘:

df = pd.DataFrame({'filename':list_a,'image_features':list_b})
df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")

因此,数据帧将被pickle并保存在每个进程中,而不是在它退出之后。进程一旦退出就会从内存中清除,因此这应该可以保持低内存占用率。

Asmus
Reply   •   7 楼
Asmus    6 年前

注意:这不是一个答案,而是一个问题和建议的快速列表

  • 你在用 ThreadPool() from multiprocessing.pool ? 这并没有很好的记录(在 python3 )我宁愿用 ThreadPoolExecutor ,(另见 here
  • 尝试调试在每个循环的最末端将哪些对象保存在内存中,例如使用 this solution 它依赖于 sys.getsizeof() globals() 以及他们的记忆足迹。
  • del results (虽然我想应该不会太大)
Andy Hayden
Reply   •   8 楼
Andy Hayden    6 年前

现在,可能是50000年的某个东西非常大,导致了OOM,所以为了测试这个,我首先尝试:

file_list_chunks = list(divide_chunks(file_list_1,20000))[30000:]

如果在10000时失败,这将确认20k是否是块大小过大,或者如果在50000时再次失败,则代码有问题。。。


首先,你不需要 list 构造函数,在python中迭代而不是将整个列表生成到内存中要好得多。

file_list_chunks = list(divide_chunks(file_list_1,20000))
# becomes
file_list_chunks = divide_chunks(file_list_1,20000)

我认为您可能在这里误用了ThreadPool:

阻止任何其他任务提交到池中。完成所有任务后,工作进程将退出。

这读起来像 close 可能还有一些想法仍在运行,虽然我想这是安全的,但感觉有点不像pythonic,最好使用线程池的上下文管理器:

with ThreadPool(64) as pool: 
    results = pool.map(get_image_features,f)
    # etc.

del python中的s aren't actually guaranteed to free memory

你应该收集 之后 加入/加入后:

with ThreadPool(..):
    ...
    pool.join()
gc.collect()


锤子1

有一件事,我会考虑在这里做,而不是使用pandas数据帧和大列表是使用SQL数据库,您可以在本地使用 sqlite3

import sqlite3
conn = sqlite3.connect(':memory:', check_same_thread=False)  # or, use a file e.g. 'image-features.db'

并使用上下文管理器:

with conn:
    conn.execute('''CREATE TABLE images
                    (filename text, features text)''')

with conn:
    # Insert a row of data
    conn.execute("INSERT INTO images VALUES ('my-image.png','feature1,feature2')")

这样,我们就不必处理大型列表对象或数据帧。

results = pool.map(get_image_features, zip(itertools.repeat(conn), f))

然后,在计算完成后,您可以从数据库中选择所有,选择您喜欢的格式。E、 g.使用 read_sql .


锤子2

由于可以将start和end作为sys.args传递给python,因此可以对它们进行切片:

# main.py
# a for loop to iterate over this
subprocess.check_call(["python", "chunk.py", "0", "20000"])

# chunk.py a b
for count,f in enumerate(file_list_chunks):
    if count < int(sys.argv[1]) or count > int(sys.argv[2]):
         pass
    # do stuff

这样,子进程将正确地清理python(因为进程将被终止,所以不可能有内存泄漏)。