PyPy:在包含整数的列表中使用 None 时会严重影响性能
PyPy: Severe performance penalty when using None in a list with integers
因为我要实现的算法使用索引 1..n
并且因为将每个索引移动一个非常容易出错,所以我决定变得聪明并在每个列表的开头插入一个虚拟元素,所以我可以使用论文中的原始公式。
为简短起见,考虑这个玩具示例:
def calc(N):
nums=[0]+range(1,N+1)
return sum(nums[1:]) #skip first element
但是,我担心我的结果是虚假的,因为我可能在某处意外访问第 0 个元素而没有意识到它。所以我变得更聪明了,使用 None
而不是 0
作为第一个元素——每个使用它的算术运算都会导致运行时错误:
def calc_safe(N):
nums=[None]+range(1,N+1) #here we use "None"
return sum(nums[1:])
令人惊讶的是,这个小的变化导致了 pypy 的巨大性能损失(即使是当前的 5.8 版本)——代码变得慢了大约 10 倍!这是我机器上的时间:
pypy-5.8 cpython
calc(10**8) 0.5 sec 5.5 sec
calc_safe(10**8) 7.5 sec 5.5 sec
作为侧节点:Cpython不关心,是否使用None
。
所以我的问题是双重的:
- 显然使用
None
不是一个好主意,但为什么呢?
- 是否有可能获得
None
方法的安全性并保持性能?
编辑: 正如 Armin 所解释的,并非所有列表都是平等的,我们可以通过以下方式看到使用了哪种策略:
import __pypy__
print __pypy__.strategy(nums)
第一种情况是 IntegerListStrategy
,第二种情况是 ObjectListStrategy
。如果我们使用大整数值(如 2**100
)而不是 None
.
,也会发生同样的情况
PyPy 有一个只包含整数的列表的特例——它像 array.array
一样存储它们。如果里面有None,那么这个优化就失效了
这可能会在 PyPy 内部修复,以允许 None 作为特殊情况...
因为我要实现的算法使用索引 1..n
并且因为将每个索引移动一个非常容易出错,所以我决定变得聪明并在每个列表的开头插入一个虚拟元素,所以我可以使用论文中的原始公式。
为简短起见,考虑这个玩具示例:
def calc(N):
nums=[0]+range(1,N+1)
return sum(nums[1:]) #skip first element
但是,我担心我的结果是虚假的,因为我可能在某处意外访问第 0 个元素而没有意识到它。所以我变得更聪明了,使用 None
而不是 0
作为第一个元素——每个使用它的算术运算都会导致运行时错误:
def calc_safe(N):
nums=[None]+range(1,N+1) #here we use "None"
return sum(nums[1:])
令人惊讶的是,这个小的变化导致了 pypy 的巨大性能损失(即使是当前的 5.8 版本)——代码变得慢了大约 10 倍!这是我机器上的时间:
pypy-5.8 cpython
calc(10**8) 0.5 sec 5.5 sec
calc_safe(10**8) 7.5 sec 5.5 sec
作为侧节点:Cpython不关心,是否使用None
。
所以我的问题是双重的:
- 显然使用
None
不是一个好主意,但为什么呢? - 是否有可能获得
None
方法的安全性并保持性能?
编辑: 正如 Armin 所解释的,并非所有列表都是平等的,我们可以通过以下方式看到使用了哪种策略:
import __pypy__
print __pypy__.strategy(nums)
第一种情况是 IntegerListStrategy
,第二种情况是 ObjectListStrategy
。如果我们使用大整数值(如 2**100
)而不是 None
.
PyPy 有一个只包含整数的列表的特例——它像 array.array
一样存储它们。如果里面有None,那么这个优化就失效了
这可能会在 PyPy 内部修复,以允许 None 作为特殊情况...