Python - 如何在不占用过多内存和减慢处理速度的情况下构建和访问大量数据集

2 投票
2 回答
1583 浏览
提问于 2025-04-17 04:41

我正在用Python写一个脚本,目的是检查一个专有的ESRI数据库表里的数据质量。这个脚本的功能不是去修改那些不合法的数据,而是简单地把这些不合法的数据通过一个csv文件报告给用户。我使用ESRI的ArcPy包来访问每一条记录,方法是用arcpy.SearchCursor。SearchCursor是访问ESRI格式中每一条记录的唯一方式。

在查看每一条记录时,我会进行多次数据质量检查,以验证特定的业务逻辑。其中一个检查就是查找某些字段中的重复数据。其中一个字段可能是几何数据。我通过为每个字段创建一个空的容器对象来实现这一点,然后在检查每条记录时使用以下逻辑。

for field in dupCheckFields:
    if row.getValue(field) in fieldValues[field]: dupValues.add(row.getValue(idField))
    else: fieldValues[field].append(row.getValue(field))

上面的代码是我使用的基本逻辑示例。问题在于,这些表可能包含从5000条记录到1000万条记录不等。我要么内存不够用,要么性能变得非常慢。

我尝试过以下几种容器类型:集合、列表、字典、ZODB + BList和Shelve。

对于内存中的类型(集合、列表、字典),一开始处理速度很快,但随着进程的进行,速度会变得非常慢。如果表中的记录很多,我会遇到内存不足的问题。而对于持久化的数据类型,我不会遇到内存不足的问题,但处理速度非常慢。

我只需要在脚本运行时使用这些数据,任何持久化的数据文件在完成后都会被删除。

问题是:有没有更好的容器类型,可以在不大幅影响性能的情况下,提供低内存存储大量数据的能力?

系统:Win7 64位,Python 2.6.5 32位,4GB内存

提前感谢你的帮助。

编辑:

示例SQLite代码:

import sqlite3, os, arcpy, timeit

fc = r"path\to\feature\class"

# test feature class was in ESRI ArcSDE format and contained "." characters separating database name, owner, and feature class name
fcName = fc.split(".")[-1]

# convert ESRI data types to SQLite data types
dataTypes = {"String":"text","Guid":"text","Double":"real","SmallInteger":"integer"}

fields = [(field.name,dataTypes[field.type]) for field in arcpy.ListFields(fc) if field.name != arcpy.Describe(fc).OIDFieldName]

# SQL string to create table in SQLite with same schema as feature class
createTableString = """create table %s(%s,primary key(%s))""" % (fcName,",\n".join('%s %s' % field for field in fields),fields[0][0])

# SQL string to insert data into SQLite table
insertString = """insert into %s values(%s)""" % (fcName, ",".join(["?" for i in xrange(len(fields))]))

# location to save SQLite database
loc = r'C:\TEMPORARY_QC_DATA'

def createDB():
    conn = sqlite3.connect(os.path.join(loc,'database.db'))
    cur = conn.cursor()

    cur.execute(createTableString)

    conn.commit()

    rows = arcpy.SearchCursor(fc)

    i = 0
    for row in rows:
        try:
            cur.execute(insertString, [row.getValue(field[0]) for field in fields])
            if i % 10000 == 0:
                print i, "records"
                conn.commit()
            i += 1
        except sqlite3.IntegrityError: pass
    print i, "records"

t1 = timeit.Timer("createDB()","from __main__ import createDB")

print t1.timeit(1)

不幸的是,我不能分享我用这段代码测试的数据,不过它是一个包含大约10个字段和大约700万条记录的ESRI ArcSDE地理数据库表。

我尝试使用timeit来测量这个过程花了多长时间,但经过2小时的处理,只有120,000条记录完成。

2 个回答

0

我觉得可以考虑把一些持久性的数据(比如已知的字段值和计数)存储在SQLite数据库里。当然,这样做是要在内存使用和性能之间做一个权衡。

如果你使用一种支持同时访问的存储机制,那么你可能可以通过使用 multiprocessing 来并行处理你的数据。处理完成后,可以从数据库中生成一个错误的总结。

0

如果你把哈希值存储在(压缩的)文件里,你可以逐个读取这些哈希值,来比较它们,看看有没有重复的。逐个读取通常对内存的要求很低——你可以设置一个缓冲区,比如每次读取一行哈希记录。这样做的缺点一般是速度会慢一些,特别是如果你还加了压缩。不过,如果你按照某种标准对文件进行排序,那么你可能可以更快地读取未压缩的内容,从而更快地比较记录。

撰写回答