Dji*_*eus 7 python optimization iterator pytables
我有一个Python程序,它使用Pytables并以这种简单的方式查询表:
def get_element(table, somevar):
rows = table.where("colname == somevar")
row = next(rows, None)
if row:
return elem_from_row(row)
Run Code Online (Sandbox Code Playgroud)
为了减少查询时间,我决定尝试对表进行排序table.copy(sortby='colname').这确实改善了查询时间(花费where),但它将next()内置函数花费的时间增加了几个数量级!可能是什么原因?
仅当表中有另一列时,才会发生此减速,并且减速随着该列的元素大小而增加.
为了帮助我理解这个问题,并确保这与我的程序中的其他内容无关,我做了这个最小的工作示例来重现问题:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import tables
import time
import sys
def create_set(sort, withdata):
#Table description with or without data
tabledesc = {
'id': tables.UIntCol()
}
if withdata:
tabledesc['data'] = tables.Float32Col(2000)
#Create table with CSI'ed id
fp = tables.open_file('tmp.h5', mode='w')
table = fp.create_table('/', 'myset', tabledesc)
table.cols.id.create_csindex()
#Fill the table with sorted ids
row = table.row
for i in xrange(500):
row['id'] = i
row.append()
#Force a sort if asked for
if sort:
newtable = table.copy(newname='sortedset', sortby='id')
table.remove()
newtable.rename('myset')
fp.flush()
return fp
def get_element(table, i):
#By construction, i always exists in the table
rows = table.where('id == i')
row = next(rows, None)
if row:
return {'id': row['id']}
return None
sort = sys.argv[1] == 'sort'
withdata = sys.argv[2] == 'withdata'
fp = create_set(sort, withdata)
start_time = time.time()
table = fp.root.myset
for i in xrange(500):
get_element(table, i)
print("Queried the set in %.3fs" % (time.time() - start_time))
fp.close()
Run Code Online (Sandbox Code Playgroud)
这里有一些控制台输出显示数字:
Run Code Online (Sandbox Code Playgroud)$ ./timedset.py nosort nodata Queried the set in 0.718s $ ./timedset.py sort nodata Queried the set in 0.003s $ ./timedset.py nosort withdata Queried the set in 0.597s $ ./timedset.py sort withdata Queried the set in 5.846s
一些说明:

next函数,而是使用a for row in rows并且相信只有一个结果,则仍然会发生减速.通过某种id(排序或不排序)从表中访问元素听起来像一个基本功能,我必须错过使用pytables执行它的典型方法.它是什么?为什么这么可怕的放缓?这是我应该报告的错误吗?
我终于明白是怎么回事了。
根本原因是一个错误,它在我这边:在进行排序之前,我没有刷新数据。结果,副本基于不完整的数据,新的排序表也是如此。这就是导致速度减慢的原因,并且在适当的时候进行冲洗会导致不那么令人惊讶的结果:
...
#Fill the table with sorted ids
row = table.row
for i in xrange(500):
row['id'] = i
row.append()
fp.flush() # <--
#Force a sort if asked for
if sort:
newtable = table.copy(newname='sortedset', sortby='id')
table.remove()
newtable.rename('myset')
fp.flush() # <--
return fp
...
Run Code Online (Sandbox Code Playgroud)
当我决定检查和比较“未排序”与“已排序”表的结构和数据时,我意识到自己的错误。我注意到在排序的情况下,表的行数较少。该数字看似随机变化,从 0 到大约 450,具体取决于数据列的大小。此外,在排序表中,所有行的 id 都设置为 0。我猜想在创建表时,pytables 会初始化列,并且可能会也可能不会预先创建具有某些初始值的某些行。这个“可能或可能不”可能取决于行的大小和计算的chunksize。
结果,当查询排序表时,除了 的查询之外的所有查询都id == 0没有结果。我最初认为引发和捕获StopIteration错误是导致速度减慢的原因,但这并不能解释为什么速度减慢取决于数据列的大小。
在阅读了 pytables 中的一些代码(特别是table.py和tableextension.pyx)后,我认为发生的情况如下:当对列进行索引时,pytables 将首先尝试使用该索引来加快搜索速度。如果找到一些匹配的行,则仅读取这些行。但是,如果索引表明没有行与查询匹配,则出于某种原因,pytables 会回退到“内核内”搜索,该搜索会迭代并读取所有行。这需要在多个 I/O 中从磁盘读取完整的行,这就是数据列的大小很重要的原因。同样,在该列的特定大小下,pytables 不会在磁盘上“预先创建”一些行,从而导致排序表中根本没有行。这就是为什么在图表上,当列大小低于 525 时,搜索速度非常快:迭代 0 行并不需要太多时间。
我不清楚为什么迭代器会回退到“内核”搜索。如果搜索到的 id 明显超出了索引范围,我看不出有任何理由去搜索它...... 编辑:仔细查看代码后,发现这是因为一个错误。它存在于我正在使用的版本(3.1.1)中,但已在 3.2.0 中修复。
真正让我哭泣的是,我只在问题的例子中复制之前忘记了冲水。在我的实际程序中,这个错误不存在!我也不知道,但在调查问题时发现,默认情况下 pytables 不会传播索引。这必须明确要求propindexes=True。这就是为什么在我的应用程序中排序后搜索速度变慢的原因......
这个故事的寓意是: