我正在编写自己的稀疏(一维)数组类,但我遇到了一些性能问题。分析表明其中一个瓶颈是我的__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模块的答案。我的一个要求是能够快速迭代(非关键)非零条目。)
答案 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”
更快