【问题标题】:Why is querying a table so much slower after sorting it?为什么排序后查询表的速度如此之慢?
【发布时间】:2015-10-03 11:17:11
【问题描述】:

我有一个 Python 程序,它使用Pytables 并以这种简单的方式查询一个表:

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

一些注意事项:

  • 实际上在所有情况下都对行进行了排序,因此它似乎与知道排序的表相关联,而不仅仅是正在排序的数据。
  • 如果不是创建文件,而是从磁盘读取文件,结果相同。
  • 仅当数据列存在时才会出现此问题,即使我从未写入或读取它。我注意到当列的大小(浮点数)增加时,时间差“分阶段”增加。减速必须与内部数据移动或 I/O 相关联:
  • 如果我不使用 next 函数,而是使用 for row in rows 并相信只有一个结果,那么仍然会出现减速。

通过某种 id(排序或未排序)访问表中的元素听起来像是一个基本功能,我一定错过了使用 pytables 执行此操作的典型方法。它是什么? 为什么会出现如此可怕的放缓?这是我应该报告的错误吗?

【问题讨论】:

    标签: python optimization iterator pytables


    【解决方案1】:

    我终于明白是怎么回事了。

    长话短说

    根本原因是一个错误,它在我这边:在进行排序的情况下,我没有在制作副本之前刷新数据。结果,副本基于不完整的数据,新的排序表也是如此。这就是导致速度变慢的原因,而在适当的时候冲洗会导致不那么令人惊讶的结果:

    ...
    #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
    ...
    

    但是为什么呢?

    当我决定检查和比较“未排序”表与“已排序”表的结构和数据时,我意识到自己的错误。我注意到在排序的情况下,表格的行数更少。根据数据列的大小,该数字似乎从 0 到大约 450 随机变化。此外,在排序表中,所有行的 id 都设置为 0。我猜在创建表时,pytables 会初始化列,并且可能会或可能不会预先创建一些具有某些初始值的行。这个“可能会或可能不会”可能取决于行的大小和计算出的chunksize

    因此,在查询排序表时,除了id == 0 之外的所有查询都没有结果。我最初认为raising and catching the StopIteration error 是导致减速的原因,但这并不能解释为什么减速取决于数据列的大小。

    在阅读了pytables中的一些代码(尤其是table.pytableextension.pyx)之后,我认为会发生以下情况:当一个列被索引时,pytables会首先尝试使用这个索引来加紧搜索。如果找到一些匹配的行,则只会读取这些行。但是,如果索引表明没有行与查询匹配,出于某种原因,pytables 会回退到“内核中”搜索,该搜索会迭代并读取所有行。这需要在多个 I/O 中从磁盘读取完整行,这就是数据列的大小很重要的原因。同样在该列的特定大小下,pytables 没有在磁盘上“预先创建”一些行,从而导致一个完全没有行的排序表。这就是为什么在图表上当列大小低于 525 时搜索非常快的原因:迭代 0 行并不需要太多时间。

    我不清楚为什么迭代器会退回到“内核”搜索。如果搜索到的 id 明显超出了索引范围,我看不出有什么理由去搜索它…… 编辑: 仔细查看代码后,事实证明这是因为一个错误。它存在于我正在使用的版本(3.1.1)中,但一直是fixed in 3.2.0

    讽刺

    真正让我哭泣的是,我只是在问题的例子中忘记了在复制之前冲水。在我的实际程序中,这个错误不存在!我也不知道,但在调查这个问题时发现,默认情况下 pytables 不传播索引。这必须通过propindexes=True 明确要求。这就是为什么在我的应用程序中排序后搜索变慢的原因...

    故事的寓意:

    • 索引很好:使用它
    • 但不要忘记在对表格进行排序时传播它们
    • 在读取之前确保您的数据在磁盘上...

    【讨论】:

      猜你喜欢
      • 2018-11-11
      • 2016-12-04
      • 2012-03-02
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 2020-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多