2012-12-21 102 views
3

我有一个包含tool_id,时间和消息的元组列表。我想从这个列表中选择消息匹配某个字符串的所有元素,以及时间在该工具的任何匹配消息的差异范围内的所有其他元素。如何让我的代码更高效?

这里是我当前如何这样做:

# record time for each message matching the specified message for each tool 
messageTimes = {} 
for row in cdata: # tool, time, message 
    if self.message in row[2]: 
     messageTimes[row[0], row[1]] = 1 

# now pull out each message that is within the time diff for each matched message 
# as well as the matched messages themselves 

def determine(tup): 
    if self.message in tup[2]: return True  # matched message 

    for (tool, date_time) in messageTimes: 
     if tool == tup[0]: 
      if abs(date_time-tup[1]) <= tdiff: 
       return True 

    return False 


cdata[:] = [tup for tup in cdata if determine(tup)] 

此代码的工作,但运行它花费的时间太长 - 例如当cdata有600,000个元素(这是我的应用程序的典型特征)需要2个小时才能运行。

该数据来自数据库。最初我只是使用SQL获取我想要的数据,但这也花了太长时间。我只是选择了我想要的消息,然后为每个人做了另一个查询,以获得每个时间差异内的数据。这导致了数以万计的查询。所以我改变了它一次拉出所有可能的匹配,然后用python处理它,认为这会更快。也许我错了。

任何人都可以给我一些关于加快速度的建议吗?

更新我的帖子,以显示我在SQL中的建议。

我在SQL中做的事很简单。第一个查询是这样的:

SELECT tool, date_time, message 
FROM event_log 
WHERE message LIKE '%foo%' 
AND other selection criteria 

这是足够快,但它可能会返回20或30万行。于是我在结果集中循环,并为每行跑这样的查询(其中DT和t是从一排DATE_TIME和工具从上面的选择):

SELECT date_time, message 
FROM event_log 
WHERE tool = t 
AND ABS(TIMESTAMPDIFF(SECOND, date_time, dt)) <= timediff 

这是需要大约一小时。

我也试过在一个嵌套查询中,内部查询从我的第一个查询中选择了行,而外部查询选择了时间差异行。这花了更长的时间。

因此,现在我选择了没有LIKE'%foo%'子句的消息,我找回600,000行并试图从python中取出所需的行。

+7

我不写这作为一个答案,因为它是没有,但我的经验,你应该尝试做的就像你在SQL中一样。该语言和环境针对从数据库中分拣和挑选数据进行了优化。如果有什么可能,你可以发布你如何在SQL中做到这一点,我们可以尝试优化。 – Mathias

+0

+1给Mathias。与查询后接子查询不同,您应该使用连接(或者,如果这不可能,SQL中的子查询)执行查询。如果这需要太长时间,那几乎肯定只是你错过了一个关键指标。 – abarnert

+0

我已更新我的帖子以显示我在SQL中做了什么。没有任何索引可以帮助解决这个问题。 –

回答

0

我做了2种变化和现在20分钟查询服用3分:

found = defaultdict(int) 
def determine(tup): 
    if self.message in tup[3]: return True  # matched message 

    times = messageTimes[tup[0]] 
    idx = found[tup[0]] 
    le = bisect.bisect_right(times, tup[1], idx) 
    idx = le 
    return (le and tup[1]-times[le-1] <= tdiff) or (le != len(times) and times[le]-tup[1] <= tdiff) 
2

对于表格数据,您不能越过Python pandas库,该库包含针对此类查询的高度优化的代码。

+0

谢谢,我会检查出 –

6

优化SQL的方法是在一个查询中完成所有操作,而不是遍历20K行并为每个查询执行另一个查询。

通常这意味着您需要添加一个JOIN,或偶尔需要一个子查询。是的,只要你重命名一个或两个副本,你就可以加入一个表格。所以,这样的事情:

SELECT el2.date_time, el2.message 
FROM event_log as el1 JOIN event_log as el2 
WHERE el1.message LIKE '%foo%' 
AND other selection criteria 
AND el2.tool = el1.tool 
AND ABS(TIMESTAMPDIFF(SECOND, el2.datetime, el1.datetime)) <= el1.timediff 

现在,这可能不会足够快开箱即用,所以有两个步骤来改善它。

首先,查找明显需要编制索引的列。显然tooldatetime需要简单的索引。 message可能受益于一个简单的索引,或者如果你的数据库有更有趣的东西,也许更有趣,但鉴于最初的查询足够快,你可能不需要担心它。

有时候,这是不够的。但通常情况下,你无法正确猜测一切。还有可能需要重新排列查询的顺序等。因此,您需要查询EXPLAIN,并查看数据库引擎正在执行的步骤,并查看它在何时执行缓慢的迭代查找它可以做一个快速索引查找,或者它在一个小集合之前迭代大集合。

+0

打我吧^^ –

+0

我曾与一个子查询试了一下,但花了一个多小时。我会玩你的查询,看看它是如何工作的。 –

+0

工具和日期时间有一个索引。你正在做的一个varchar字段上的索引将无济于事。 DB是MySQL。 –

0

我改变我的代码如下解决了这个问题:

- 首先我做messageTimes由工具键入列表的字典:在我用平分确定功能

messageTimes = defaultdict(list) # a dict with sorted lists 

for row in cdata: # tool, time, module, message 
    if self.message in row[3]: 
     messageTimes[row[0]].append(row[1]) 

- 那么:

def determine(tup): 
    if self.message in tup[3]: return True  # matched message 

    times = messageTimes[tup[0]] 
    le = bisect.bisect_right(times, tup[1]) 
    ge = bisect.bisect_left(times, tup[1]) 
    return (le and tup[1]-times[le-1] <= tdiff) or (ge != len(times) and times[ge]-tup[1] <= tdiff) 

通过这些更改,超过2小时的代码花费了20分钟,甚至更好,花了40分钟的查询耗时8秒!