为什么循环枚举比生成器快得多?

时间:2012-07-23 11:14:17

标签: python optimization generator enumeration

最近,我在this主题的Jon Clements的帮助下发现以下代码的执行时间差异很大。

你知道为什么会这样吗?

注释: self.stream_data是一个带有许多零和int16值的向量元组,create_ZS_data方法正在执行所谓的ZeroSuppression。

环境
输入:许多(3.5k)小文件(每个约120kb)
操作系统: Linux64
Python ver 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 ] )

Profiler信息:

   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>)

Jon的解决方案:

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])


Profiler信息:

   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)

2 个答案:

答案 0 :(得分: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”的实现确实如此。

发电机不是保证效率的秘诀。在这种特殊情况下,有135kb的数据已经被读入核心,一个构造不良的发电机似乎在耗费你的成本。如果您想要简洁的矩阵运算,请使用APL;如果你想要可读的代码,不要在Python中争取狂热的最小化。

答案 1 :(得分:2)

您可以轻松地将Jon的解决方案重写为生成器:

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)

我强烈期望这与Jon基于循环的解决方案的行为相同,这表明性能差异低至算法实现的中等规模细节。