为什么创建一个从0到log(len(list),2)的范围这么慢?

时间:2013-04-29 22:34:40

标签: python performance sage

我不知道为什么会发生这种情况。我在搞乱一些列表,我需要一个从{0到for的{​​{1}}循环,其中n是列表的长度。但是代码速度非常慢,所以经过一番研究后我发现问题出现在范围生成中。演示示例代码:

log(n, 2)

输出

n = len([1,2,3,4,5,6,7,8])
k = 8
timeit('range(log(n, 2))', number=2, repeat=3) # Test 1
timeit('range(log(k, 2))', number=2, repeat=3) # Test 2

测试次数很少(我不希望它运行超过10分钟),但它已经显示2 loops, best of 3: 2.2 s per loop 2 loops, best of 3: 3.46 µs per loop 比仅使用整数的对数的对应物慢几个数量级。这真的很令人惊讶,我不知道为什么会发生这种情况。可能是我的电脑上的问题,可能是Sage问题或Python错误(我在Python上没有尝试过相同的问题)。

使用range(log(n, 2))代替xrange也无济于事。此外,如果您使用range获得数字,则测试1以2的相同速度运行。

有人知道会发生什么吗? 谢谢!

4 个答案:

答案 0 :(得分:13)

好悲伤 - 我认识到这一点。它与我的一个trac #12121有关。首先,由于无聊的原因,使用Python int而不是Sage Integer会产生额外的开销:

sage: log(8, 2)
3
sage: type(log(8, 2))
sage.rings.integer.Integer
sage: log(8r, 2)
log(8)/log(2)
sage: type(log(8r, 2))
sage.symbolic.expression.Expression
sage: %timeit log(8, 2)
1000000 loops, best of 3: 1.4 us per loop
sage: %timeit log(8r, 2)
1000 loops, best of 3: 404 us per loop

r后缀表示“原始”,并阻止Sage预处理器将文字2包装到Integer(2)

然后它变得奇怪。为了产生range消耗的int,Sage必须弄清楚如何将log(8)/log(2)变为3,并且事实证明她做了最糟糕的事情。抄袭我原来的诊断(比照适用):

首先,她检查这个对象是否有自己的方式来获取int,但事实并非如此。所以她用log(8)/ log(2)构建了一个RealInterval对象,事实证明这是她能做的最糟糕的事情!她检查区间的下部和上部是否同意[在地板上,我的意思](这样她就知道地板是什么)。但在这种情况下,因为它确实是一个整数!这总是看起来像:

sage: y = log(8)/log(2)
sage: rif = RealIntervalField(53)(y)
sage: rif
3.000000000000000?
sage: rif.endpoints()
(2.99999999999999, 3.00000000000001)

这两个界限的楼层并不相等,所以Sage决定她还没有解决问题,她不断将精度提高到20000位,看她是否可以证明它们是......但是通过建设,它永远不会起作用。最后,她放弃并试图简化它,这成功了:

sage: y.simplify_full()
3

证明没有言语,这是完全可分割的案件的反常属性:

sage: %timeit range(log(8r, 2))
1 loops, best of 3: 2.18 s per loop
sage: %timeit range(log(9r, 2))
1000 loops, best of 3: 766 us per loop
sage: %timeit range(log(15r, 2))
1000 loops, best of 3: 764 us per loop
sage: %timeit range(log(16r, 2))
1 loops, best of 3: 2.19 s per loop

答案 1 :(得分:1)

Python 2允许范围(some_float),但不推荐使用它并且在python 3中不起作用。

代码示例未指定输出。但我们可以走过它。首先,timeit需要一个完整的脚本,脚本调用timeit中的导入不会被使用:

>>> timeit('range(log(8,2))')
  Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.6/timeit.py", line 226, in timeit
    return Timer(stmt, setup, timer).timeit(number)
  File "/usr/lib64/python2.6/timeit.py", line 192, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
NameError: global name 'log' is not defined

如果您将导入添加到正在计时的脚本中,则会包含设置时间:

>>> timeit('from math import log;range(log(8,2))')
3.7010221481323242

如果你将导入移动到设置,它会更好,但是一次性的定时是众所周知的不准确:

>>> timeit('range(log(8,2))',setup='from math import log')
1.9139349460601807

最后,运行它多次,你得到一个很好的数字:

>>> timeit('range(log(8,2))',setup='from math import log',number=100)
0.00038290023803710938

答案 2 :(得分:1)

这看起来像是一个Sage bug。

我创建了一个新的笔记本并做了这个:

n = len([1,2,3,4,5,6,7,8])
k = 8
timeit('range(log(n, 2))', number=2, repeat=3) # Test 1
timeit('range(log(len([1,2,3,4,5,6,7,8]), 2))', number=2, repeat=3) # Test 1.5
timeit('range(log(k, 2))', number=2, repeat=3) # Test 2

测试1.5与测试1一样慢。但如果你以任何方式将其分解 - 取消range,甚至添加m=n+0并使用m代替{ {1}},它下降到微秒。

很明显,Sage在评估表达时会尝试做一些复杂的事情,并且感到困惑。


要验证这一点,请参阅普通的ipython:

n

他们都像你期望的一样快。


那么......你怎么办呢?

好吧,您可能想要尝试追踪Sage错误并将其归档上游。但与此同时,您可能需要在代码中使用解决方法。

如上所述,仅仅执行n = len([1,2,3,4,5,6,7,8]) k = 8 %timeit 'range(log(n, 2))' %timeit 'range(log(len([1,2,3,4,5,6,7,8]), 2))' %timeit 'range(log(k, 2))' 并使用m = n+0代替m似乎可以加快速度。看看它是否适合你?

答案 3 :(得分:-1)

首先使用log(x, 2)(又名ld())并不是一个好主意。我建议使用移位int值来实现ld()

n = len(array)
while n:
  n >>= 1
  # perform the loop stuff

通过这种方式,您可以使用range()log()避免所有这些违规行为。

在正常情况下,调用log()应该比在int上进行简单的位移动需要更多的时间。例子:

>>> timeit('for i in range(int(math.log(8, 2))): pass', setup='import math')
0.6762251853942871
>>> timeit('n = 8\nwhile n:\n  n >>= 1')
0.24107813835144043

n的值越大,差异越小。对于n = 10000,我得到了0.8163230419158936和0.8106038570404053,但这应该是因为与循环初始化相比,循环体将占用大部分时间。