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

发布于 2021-01-29 16:00:19

我试图迭代超过100,000个图像并捕获一些图像功能,并将所得的dataFrame作为pickle文件存储在磁盘上。

不幸的是,由于RAM的限制,我被迫将图像分成20,000个大块,并对它们执行操作,然后再将结果保存到磁盘上。

在开始循环以处理下一个20,000图像之前,下面编写的代码应该保存20,000图像的结果数据框。

但是-这似乎没有解决我的问题,因为在第一个for循环结束时内存没有从RAM中释放

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

我尝试将对象保存到磁盘并调用垃圾收集器后删除这些对象,但是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")
关注者
0
被浏览
264
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    现在,可能是第50,000位的数据非常大,这导致了OOM,因此要对此进行测试,我将首先尝试:

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

    如果它在10,000处失败,这将确认20k的块大小是否太大,或者如果再次在50,000处失败,则代码存在问题…


    好的,进入代码…

    首先,您不需要显式list构造函数,在python中进行迭代比将整个列表生成到内存中要好得多。

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

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

    阻止将更多任务提交给池。所有任务完成后,工作进程将退出。

    这听起来像close有些想法仍在运行,尽管我认为这是安全的,但感觉有点不符合Python风格,最好将上下文管理器用于ThreadPool:

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

    实际上,不能保证delpython的显式s释放内存

    您应该 加入之后/之后使用:

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

    您也可以尝试将其切成小块,例如10,000甚至更小!


    锤子1

    一件事,我会考虑在这里做,而不是使用pandas
    DataFrames和大列表是使用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')")
    

    这样,我们就不必处​​理大型列表对象或DataFrame。

    您可以将连接传递给每个线程…您可能需要做些奇怪的事情:

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

    然后,计算完成后,您可以从数据库中选择所有格式,并选择所需的格式。例如,使用read_sql


    锤子2

    在这里使用一个子进程,而不是在python的“ shell out”的另一个实例中运行该子进程。

    由于您可以将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(由于进程将终止,因此不会有内存泄漏)。


    我敢打赌,Hammer
    1是必经之路,感觉就像您要粘贴大量数据,并不必要地将其读取到python列表中,而使用sqlite3(或其他一些数据库)完全可以避免这种情况。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看