2012-07-23 78 views
3

最近我在Jon Clements的帮助下发现了this线程,下面的代码执行时间非常不同。为什么遍历枚举比发生器快得多?

你知道为什么会发生这种情况吗?

评论: self.stream_data是一个具有许多零和int16值的向量元组,create_ZS_data方法执行所谓的ZeroSuppression。

环境
输入:许多(3.5K)小文件(〜120KB每)
OS: LINUX64
的Python版本2.6.8

解决方案基于发电机:

def create_ZS_data(self): 
    self.ZS_data = ([column, row, self.stream_data[column + row * self.rows ]] 
        for row, column in itertools.product(xrange(self.rows), xrange(self.columns)) 
        if self.stream_data[column + row * self.rows ]) 

探查信息:

ncalls tottime percall cumtime percall filename:lineno(function) 
    3257 1.117 0.000 71.598 0.022 decode_from_merlin.py:302(create_ZS_file) 
    463419 67.705 0.000 67.705 0.000 decode_from_merlin.py:86(<genexpr>) 

乔恩的解决方案:

create_ZS_data(self): 
    self.ZS_data = list() 
    for rowno, cols in enumerate(self.stream_data[i:i+self.columns] for i in xrange(0, len(self.stream_data), self.columns)): 
     for colno, col in enumerate(cols): 
      # col == value, (rowno, colno) = index 
      if col: 
       self.ZS_data.append([colno, rowno, col]) 


探查信息:

ncalls tottime percall cumtime percall filename:lineno(function) 
    3257 18.616 0.006 19.919 0.006 decode_from_merlin.py:83(create_ZS_data) 
+1

看起来相当明显,它与第一个解决方案中的呼叫次数有关,不是吗? – martineau 2012-07-23 11:21:51

+0

是的,但不是生来处理大量呼叫的生成器?他们通常被推荐为大列表的替代品。 – Michal 2012-07-23 11:28:14

+1

对于简单情况,生成器*通常*以CPU为代价减少内存使用量。 – Hamish 2012-07-23 11:36:59

回答

4

我看了一下前面的讨论;你似乎感到困惑的是,你的聪明理解在循环中并不像源代码中的字符那样高效。我没有指出然后,这将是我的首选实现阅读:

def sparse_table_elements(cells, columns, rows): 
    ncells = len(cells) 
    non_zeros = list() 
    for nrow in range(0, ncells, columns): 
     row = cells[nrow:nrow+columns] 
     for ncol, cell in enumerate(row): 
      if cell: 
       non_zeros.append([ncol, nrow, cell]) 
    return non_zeros 

我没有测试它,但我能理解它。有几件事情在我身上跳出来,因为潜在的效率低下。重新计算两个不变单调“无聊”指数笛卡尔乘积一定是昂贵的:

itertools.product(xrange(self.rows), xrange(self.columns)) 

你再使用结果[(0, 0), (0, 1), ...]从源做单元素索引:

stream_data[column + row * self.rows] 

这也是比“Jon's”实现所处理的更大的片更昂贵。

发电机并不能保证效率。在这种特殊情况下,如果有135kb的数据已经被读入核心,那么构造不良的发生器似乎会让你付出代价。如果您想要简洁的矩阵操作,请使用APL;如果你想要可读的代码,不要为Python中的狂暴最小化而努力。

+0

此外,您引用的“单元素切片” - 我简单地称为对“stream_data”的索引访问 - 实际上是在生成器/列表comp版本中完成_twice_。 – martineau 2012-07-23 16:53:23

+0

就术语达成一致;固定,谢谢。 – msw 2012-07-23 17:25:21

2

你可以平凡改写乔恩的解决方案作为发电机:

def create_ZS_data(self): 
    self.ZS_data = ([colno, rowno, col] 
        for rowno, cols in enumerate(self.stream_data[i:i+self.columns] 
               for i in xrange(0, len(self.stream_data), self.columns)) 
        for colno, col in enumerate(cols) 
        if col) 

我强烈希望这行为相同,乔恩的循环为基础的解决方案,这表明在性能上的差异降低到中等规模的细节的算法实现。