在Python中为稀疏数组优化`__getitem__`和`__setitem__`

时间:2011-03-28 20:02:03

标签: python optimization

我正在编写自己的稀疏(一维)数组类,但我遇到了一些性能问题。分析表明其中一个瓶颈是我的__getitem____setitem__实施,特别是,似乎其中一个罪魁祸首可能是我对isinstance的使用。目前我在isinstance中有__getitem__的5次调用,我从cProfile中获取了以下数据(摘录):

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    86462    0.076    0.000    0.084    0.000 sparse.py:107(__setitem__)
   189730    0.147    0.000    0.166    0.000 sparse.py:45(__getitem__)
   276366    0.028    0.000    0.028    0.000 {built-in method isinstance}

我的__getitem__实现了切片以及数组访问,所以我怀疑某些类型的内省 是必要的...但我想知道是否isinstance真的是最好的方法吗?

另一方面,我的__setitem__不支持切片(在任何情况下都只调用isinstance一次),所以我不知道如何让它更快。每行分析数据如下:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   108                                              @profile
   109                                              def __setitem__(self, key, value):
   110     88705       121012      1.4     23.0         if not isinstance(key, int):
   111                                                      raise TypeError('list indices must be be integers')
   112                                                  
   113     88705        95905      1.1     18.3         if key >= self._length:
   114                                                      raise IndexError('list index out of range')
   115                                                  
   116     88705        85328      1.0     16.2         if key < 0:
   117                                                      key = self._length + key
   118                                                  
   119     88705        89186      1.0     17.0         if value == self._default:
   120     35043        37087      1.1      7.1             if key in self._entries:
   121     35042        39359      1.1      7.5                 del self._entries[key]
   122                                                  else:
   123     53662        57527      1.1     10.9             self._entries[key] = value

(我也愿意接受一个建议合适的快速稀疏数组Python模块的答案。我的一个要求是能够快速迭代(非关键)非零条目。)

4 个答案:

答案 0 :(得分:5)

要回答您的直接问题,isinstance()是一个缓慢的调用,因为该名称是全局的。只需将isinstance=isinstance添加到__setitem__()的签名即可显着加快速度,如下所示:

def __setitem__(self, key, value, isinstance=isinstance):
    # und so weiter

这会将全局名称转换为本地名称,在运行时查找速度要快得多。作为奖励,本地名称在函数定义时绑定到内置isinstance函数,因此在调用变量时没有开销初始化。

然而,正如其他人所指出的那样,在您展示的代码中,您可能甚至不需要该调用,但可以简单地尝试将密钥转换为int,或者甚至跳过该代码。 (但是,由于int=int也是一个全局名称,因此可以通过向方法签名添加int来提高速度......)

但是如果要进行错误检查,还应该测试索引是否小于零。如果长度为50并且用户想要项目-100怎么办? : - )

答案 1 :(得分:2)

你为什么不试着替换......

if not isinstance(key,int):
    raise TypeError('list indices must be integers')

...与...

key = int(key)

我相信这最终将成为一个更快的操作,似乎它会更灵活,因为如果有人将你的函数交给一个可以转换为整数的东西,它仍然可以工作。


您也可以考虑简单地检查他们的密钥类型。简单地说明使用除int以外的任何内容是未定义的行为,然后用户有责任确保他们正确使用它。

答案 2 :(得分:0)

如何摆脱异常?


def __setitem__(self, key, value):
   # This checks that:
   # - key is an integer (or can be converted to an integer)
   # - key is replaced by an appropriate positive value when < 0
   # - key is made = self._length when key >= self._length (not exactly as before)
   key = slice(key).indices(self._length)[1]

   if value == self._default:
       self._entries.pop(key, None) # assuming _entries is of type dict
   else:
       self._entries[key] = value

答案 3 :(得分:-1)

改为使用assert

if not isinstance(key, int):
   raise TypeError('list indices must be be integers')

它比“if ....:raise exception”

更快