当通过sqlalchemy和pandas to_sql
和指定的chucksize将巨大的pandas数据帧插入sqlite时,会出现内存错误。
起初,我认为这是to_sql
的问题,但是我尝试了一种变通方法,其中我不使用块大小而是使用for i in range(100): df.iloc[i * 100000:(i+1):100000].to_sql(...)
,但仍然导致错误。
在某些情况下,似乎存在内存泄漏,并且通过sqlalchemy重复插入sqlite。
通过一个最小的示例,我很难尝试复制在转换数据时发生的内存泄漏。但这非常接近。
import string
import numpy as np
import pandas as pd
from random import randint
import random
def make_random_str_array(size=10, num_rows=100, chars=string.ascii_uppercase + string.digits):
return (np.random.choice(list(chars), num_rows*size)
.view('|U{}'.format(size)))
def alt(size, num_rows):
data = make_random_str_array(size, num_rows=2*num_rows).reshape(-1, 2)
dfAll = pd.DataFrame(data)
return dfAll
dfAll = alt(randint(1000, 2000), 10000)
for i in range(330):
print('step ', i)
data = alt(randint(1000, 2000), 10000)
df = pd.DataFrame(data)
dfAll = pd.concat([ df, dfAll ])
import sqlalchemy
from sqlalchemy import create_engine
engine = sqlalchemy.create_engine('sqlite:///testtt.db')
for i in range(500):
print('step', i)
dfAll.iloc[(i%330)*10000:((i%330)+1)*10000].to_sql('test_table22', engine, index = False, if_exists= 'append')
这是在Google Colab CPU环境上运行的。
数据库本身并不会引起内存泄漏,因为我可以重新启动环境,并且先前插入的数据仍然存在,并且连接到该数据库不会导致内存增加。问题似乎是在某些情况下,通过循环to_sql
或指定了chucksize的一个to_sql
重复插入。
有没有一种方法可以运行此代码而不会导致内存使用量的最终增加?
编辑:
要完全重现错误,请运行此笔记本
https://drive.google.com/open?id=1ZijvI1jU66xOHkcmERO4wMwe-9HpT5OS
笔记本需要您将此文件夹导入Google云端硬盘的主目录
https://drive.google.com/open?id=1m6JfoIEIcX74CFSIQArZmSd0A8d0IRG8
笔记本还将安装您的Google驱动器,您需要授予其访问Google驱动器的权限。由于数据托管在我的Google驱动器上,因此导入数据不会占用您分配的任何数据。
答案 0 :(得分:5)
Google Colab实例从大约12.72GB的可用RAM开始。
创建数据帧theBigList
后,已使用约9.99GB的RAM。
这已经是一种相当不舒服的情况,因为对于
熊猫操作需要与其所操作的DataFrame一样多的额外空间。
因此,我们应该努力避免使用尽可能多的RAM,幸运的是,有一种简单的方法可以做到这一点:只需加载每个.npy
文件,并将其数据一次存储在sqlite数据库中,一次曾经创建theBigList
(请参见下文)。
但是,如果我们使用您发布的代码,则可以看到RAM使用率缓慢增加
theBigList
的大块迭代地存储在数据库中。
theBigList
DataFrame将字符串存储在NumPy数组中。但是在过程中
将字符串传输到sqlite数据库的过程中,NumPy字符串是
转换成Python字符串。这会占用更多内存。
每this Theano tutoral讨论了Python内部内存管理,
为了加快内存分配(和重用)的速度,Python使用了许多列表 小物件。每个列表将包含大小相似的对象: 列出对象的大小,范围为1到8个字节,一个为9到16个字节,以此类推。 需要创建,或者我们重复使用列表中的空闲块,或者分配一个 新的。
...重要的是这些列表永远不会缩小。
确实:如果某项(尺寸为x)被释放(由于缺乏参考而释放),则其 位置不会返回到Python的全局内存池(甚至更少) 系统),但仅标记为免费,并添加到大小免费的商品列表中 X。如果另一个兼容的对象,则该死对象的位置将被重用 需要尺寸。如果没有可用的失效对象,则会创建新的失效对象。
如果从不释放小对象的内存,那么不可避免的结论是, 像金鱼一样,这些小的对象列表只会持续增长,不会缩小, 并且应用程序的内存占用量最大 在任何给定点分配的小对象的数量。
我相信这可以准确地描述您在执行此循环时看到的行为:
for i in range(0, 588):
theBigList.iloc[i*10000:(i+1)*10000].to_sql(
'CS_table', engine, index=False, if_exists='append')
即使许多死对象的位置都被重新用于新字符串,
对于本质上随机的字符串(例如theBigList
中的字符串)来说,这是不令人难以置信的,多余的空间有时会
需要,因此内存占用量不断增长。
该过程最终达到Google Colab的12.72GB RAM限制,并且内核因内存错误而被终止。
在这种情况下,避免使用大量内存的最简单方法是永远不要实例化整个DataFrame-而是一次加载和处理DataFrame的一小块:
import numpy as np
import pandas as pd
import matplotlib.cbook as mc
import sqlalchemy as SA
def load_and_store(dbpath):
engine = SA.create_engine("sqlite:///{}".format(dbpath))
for i in range(0, 47):
print('step {}: {}'.format(i, mc.report_memory()))
for letter in list('ABCDEF'):
path = '/content/gdrive/My Drive/SummarizationTempData/CS2Part{}{:02}.npy'.format(letter, i)
comb = np.load(path, allow_pickle=True)
toPD = pd.DataFrame(comb).drop([0, 2, 3], 1).astype(str)
toPD.columns = ['title', 'abstract']
toPD = toPD.loc[toPD['abstract'] != '']
toPD.to_sql('CS_table', engine, index=False, if_exists='append')
dbpath = '/content/gdrive/My Drive/dbfile/CSSummaries.db'
load_and_store(dbpath)
可打印
step 0: 132545
step 1: 176983
step 2: 178967
step 3: 181527
...
step 43: 190551
step 44: 190423
step 45: 190103
step 46: 190551
每行的最后一个数字是该进程报告的进程消耗的内存量
matplotlib.cbook.report_memory。有许多不同的内存使用量度。在Linux上,mc.report_memory()
正在报告
the size of the physical pages of the core image的流程(包括文本,数据和堆栈空间)。
顺便说一句,您可以使用的另一个管理内存的基本技巧是使用函数。
函数终止时,将释放函数内部的局部变量。
这样可以减轻您手动调用del
和gc.collect()
的负担。