我有一个使用Pytables的Python程序,并以这种简单的方式查询表:
def get_element(table, somevar):
rows = table.where("colname == somevar")
row = next(rows, None)
if row:
return elem_from_row(row)
为了减少查询时间,我决定尝试使用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()
以下是一些显示数字的控制台输出:
$ ./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
功能,而是使用for row in rows
并相信只有一个结果,则仍会出现减速。通过某种id(排序或不排序)从表中访问元素听起来像一个基本功能,我必须错过使用pytables执行此操作的典型方法。它是什么? 为什么这么可怕的放缓?这是我应该报告的错误吗?
答案 0 :(得分:2)
我终于理解了发生了什么。
根本原因是一个错误,它就在我身边:在进行排序时,我没有在复制数据之前刷新数据。因此,副本基于未完成的数据,新的排序表也是如此。这就是造成经济放缓的原因,适当的冲洗导致了一个不太令人惊讶的结果:
...
#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
...
当我决定检查并比较表格的结构和数据时,我意识到了我的错误。#34;未排序&#34; vs&#34;排序&#34;。我注意到在排序的情况下,表的行数较少。根据数据列的大小,数字看似随机地从0变化到约450。此外,在排序表中,所有行的id都设置为0.我想在创建表时,pytables初始化列,可能会也可能不会预先创建一些具有一些初始值的行。这个&#34;可能会也可能不会&#34;可能取决于行的大小和计算的chunksize。
因此,在查询已排序的表时,除id == 0
之外的所有查询都没有结果。我最初认为raising and catching the StopIteration
error导致了减速,但这并不能解释为什么减速取决于数据列的大小。
从pytables(特别是table.py和tableextension.pyx)中读取了一些代码后,我认为会发生以下情况:当列被索引时,pytables将首先尝试使用此索引来固定搜索。如果找到一些匹配的行,则只读取这些行。但是如果索引指示没有行匹配查询,由于某种原因,pytables回退到内核中的&#34;&#34;搜索,迭代并读取所有行。这需要在多个I / O中从磁盘读取完整行,这就是数据列大小重要的原因。同样在该列的一定大小下,pytables没有&#34;预创建&#34;磁盘上的某些行,导致排序表完全没有行。这就是为什么在图表上,当列大小小于525时搜索非常快:迭代0行并不需要花费很多时间。
我不清楚为什么迭代器会回退到#34;内核&#34;搜索。如果搜索到的ID明显超出索引范围,我无论如何都看不出任何理由进行搜索...... 编辑:仔细查看代码后,事实证明这是因为一个错误。它出现在我使用的版本(3.1.1)中,但已经fixed in 3.2.0。
真正让我哭的是我在复制之前忘记了冲洗,只是在问题的例子中。在我的实际程序中,这个bug不存在!我也不知道但在调查问题时发现,默认情况下pytables不会传播索引。必须使用propindexes=True
明确要求这一点。这就是在我的应用程序中排序后搜索速度较慢的原因......
故事的道德: