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

时间:2019-05-14 08:28:28

标签: python pandas memory-management out-of-memory

我正在尝试遍历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")

9 个答案:

答案 0 :(得分:6)

现在,可能是第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.

Python aren't actually guaranteed to free memory中的显式del

您应该在加入之后 之后加入:

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(或其他数据库)完全避免了这种情况。 / p>

答案 1 :(得分:1)

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

  • 您是否正在使用ThreadPool() from multiprocessing.pool? (在python3中并没有得到很好的记录,我宁愿使用here,(另请参阅this solution
  • 尝试调试在每个循环的最后将哪些对象保存在内存中,例如使用依靠sys.getsizeof()的{​​{3}}返回所有已声明的globals()的列表及其内存占用量。
  • 也叫del results(我想虽然不应该很大)

答案 2 :(得分:1)

您的问题是您正在使用应在其中进行多处理的线程(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")

因此,数据框将被腌制并保存在每个进程内部,而不是在退出之后。进程在退出时会立即从内存中清除,因此这应该可以保持较低的内存占用。

答案 3 :(得分:0)

请勿调用list(),它正在创建内存 从divide_chunks()返回的所有内容的列表。 那就是您的内存问题可能正在发生的地方。

您不需要一次存储所有这些数据。 只需一次遍历一个文件名,这样所有数据就不会一次存储在内存中。

请发布堆栈跟踪,以便我们获得更多信息

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

我认为celery可以实现这一目标,这要感谢celery,您可以在python中轻松使用并发性和并行性。

处理图像似乎是幂等和原子的,因此可以是celery task

您可以运行a few workers来处理任务-处理图像。

此外,它还有configuration用于内存泄漏。

答案 6 :(得分:0)

我对此类问题的解决方案是使用一些并行处理工具。我更喜欢joblib,因为它甚至可以并行化本地创建的函数(这是“实现的细节”,因此最好避免将它们在模块中全局化)。我的另一条建议:请勿在python中使用线程(和线程池),而应使用进程(和进程池)-这几乎总是一个更好的主意!只需确保在joblib中创建至少包含2个进程的池,否则它将在原始python进程中运行所有程序,因此最终不会释放RAM。作业库工作进程自动关闭后,操作系统将完全释放它们分配的RAM。我最喜欢的选择武器是joblib.Parallel。如果您需要向工作人员传输大数据(即大于2GB),请使用joblib.dump(在主进程中将python对象写入文件)和joblib.load(在工作进程中读取) )。

关于del object:在python中,该命令实际上并不删除对象。它只会减少其参考计数器。当您运行import gc; gc.collect()时,垃圾收集器会自行决定要释放的内存和要分配的内存,而我还不知道一种强制它释放所有可能内存的方法。更糟糕的是,如果实际上不是由python分配一些内存,而是例如在某些外部C / C ++ / Cython / etc代码中,并且该代码未将python引用计数器与该内存相关联,那么您绝对不会有任何东西除了我上面写的内容外,您可以从python中释放它,即通过终止分配RAM的python进程,在这种情况下,可以保证它会被OS释放。这就是为什么释放python中的某些内存的唯一100%可靠的方法是运行在并行进程中分配内存的代码,然后终止该进程

答案 7 :(得分:0)

pd.DataFrame(...)可能会在某些Linux版本上泄漏(请参阅github issue and "workaround"),因此即使del df也可能无济于事。

在您的情况下,可以使用来自github的解决方案而无需对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.S。如果任何单个数据框太大,此解决方案将无济于事。这只能通过减少CHUNK_SIZE

来帮助

答案 8 :(得分:-2)

您可以尝试将文件分成多个每个20k图像的文件,然后遍历不同的文件。这将释放每个文件末尾使用的内存。
数据帧也存储在RAM中,当您将图像放入其中时,数据帧会不断增长。那可能是您的RAM问题的原因。您可以在这里https://stackoverflow.com/a/39377643/11463544

进行检查