我一次又一次地重复以下习语。我从一个大文件中读取(有时,最多有120万条记录!)并将输出存储到SQLite数据库中。将内容放入SQLite DB似乎相当快。
def readerFunction(recordSize, recordFormat, connection, outputDirectory, outputFile, numObjects):
insertString = "insert into NODE_DISP_INFO(node, analysis, timeStep, H1_translation, H2_translation, V_translation, H1_rotation, H2_rotation, V_rotation) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"
analysisNumber = int(outputPath[-3:])
outputFileObject = open(os.path.join(outputDirectory, outputFile), "rb")
outputFileObject, numberOfRecordsInFileObject = determineNumberOfRecordsInFileObjectGivenRecordSize(recordSize, outputFileObject)
numberOfRecordsPerObject = (numberOfRecordsInFileObject//numberOfObjects)
loop1StartTime = time.time()
for i in range(numberOfRecordsPerObject ):
processedRecords = []
loop2StartTime = time.time()
for j in range(numberOfObjects):
fout = outputFileObject .read(recordSize)
processedRecords.append(tuple([j+1, analysisNumber, i] + [x for x in list(struct.unpack(recordFormat, fout))]))
loop2EndTime = time.time()
print "Time taken to finish loop2: {}".format(loop2EndTime-loop2StartTime)
dbInsertStartTime = time.time()
connection.executemany(insertString, processedRecords)
dbInsertEndTime = time.time()
loop1EndTime = time.time()
print "Time taken to finish loop1: {}".format(loop1EndTime-loop1StartTime)
outputFileObject.close()
print "Finished reading output file for analysis {}...".format(analysisNumber)
当我运行代码时,似乎“循环2”和“插入数据库”是花费大部分执行时间的地方。平均“循环2”时间 0.003s ,但在某些分析中,它会运行到 50,000 次。将内容放入数据库所花费的时间大致相同: 0.004s 。目前,我每次在loop2完成后插入数据库,这样我就不用处理RAM了。
我可以做些什么来加速“循环2”?
答案 0 :(得分:2)
这主要是I / O问题。
for j in range(numberOfObjects):
fout = outputFileObject .read(recordSize)
您花费大部分时间阅读文件的少量增量位(即一次一条记录),然后使用struct
解压缩这些单独的记录。这很慢。相反,抓住你想要的所有文件的整个块,然后让struct.unpack
以C速度通过它。
你需要做一些数学计算才能找出read
的字节数,并改变你的recordFormat
格式字符串,告诉struct
如何解包整个事情。我的例子中没有足够的信息让我更准确地告诉你应该怎么做。
我还必须指出:
tuple([j+1, analysisNumber, i] + [x for x in list(struct.unpack(recordFormat, fout))])
远更加清晰地写成:
(j+1, analysisNumber, i) + struct.unpack(recordFormat, fout)
...但如果您按照我的上述建议完全删除循环,则需要重构该行。 (你可以使用zip
和enumerate
在解压缩整个东西之后将这些数据添加到每个结构成员上。
编辑示例。我将1M无符号整数打包到一个文件中。 yours()
是您的方法,mine()
是我的。
def yours():
res = []
with open('packed', 'rb') as f:
while True:
b = f.read(4)
if not b:
break
res.append(struct.unpack('I',b))
return res
def mine():
with open('packed', 'rb') as f:
return struct.unpack('1000000I',f.read())
时序:
%timeit yours()
1 loops, best of 3: 388 ms per loop
%timeit mine()
100 loops, best of 3: 6.14 ms per loop
所以,大约有2个数量级的差异。
答案 1 :(得分:1)
我认为使用mmap模块来处理内存映射文件 可能会帮助你节省两次时间。我发现太小或者 非常大的块不会节省太多,但你可以尝试看到最佳尺寸。
import mmap
def binFileRead(chunk): # the reading of binary file length size
with open(filename, "rb") as f:
for n in range(int(length/chunk)):
dd=f.read(chunk)
def mapFileRead(chunk): # the reading of memory mapped file length size
with open(filename, "r+b") as f:
mapf = mmap.mmap(f.fileno(), length, access=mmap.ACCESS_READ)
for n in range(int(length/chunk)):
offset=n*chunk
dd=mapf[offset:offset+chunk]
# dd=mapf.read(chunk)
mapf.close()
我计算了两个功能:
timeit("mapFileRead({})".format(n),"from __main__ import mapFileRead", number=1))
timeit("binFileRead({})".format(n),"from __main__ import binFileRead", number=1))
chunk=4096:
mapFileRead 0.00837285185687
binFileRead 0.0148429479166
编辑: 我认为,读取时对文件的索引访问允许使用并行读取多个记录的线程。如果你感兴趣,我可以写一个例子。
答案 2 :(得分:0)
我看到循环2中唯一的错误是使用列表理解。
不要在列表类型对象上使用[x for x in list]
。因为你在这里进行必要的迭代。它可以写成list
所以你应该写这样的东西,
processedRecords.append(
tuple([j+1, analysisNumber, i] + list(struct.unpack(recordFormat, fout))))