Python 为什么创建从0到log(len(list),2)的范围这么慢?
我不知道为什么会这样。我正在处理一些列表,我需要一个从0到Python 为什么创建从0到log(len(list),2)的范围这么慢?,python,performance,sage,Python,Performance,Sage,我不知道为什么会这样。我正在处理一些列表,我需要一个从0到log(n,2)的循环,其中n是列表的长度。但是代码速度惊人地慢,所以经过一点研究,我发现问题出在范围生成上。演示的示例代码: 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 输出 2 loops,
log(n,2)
的循环,其中n是列表的长度。但是代码速度惊人地慢,所以经过一点研究,我发现问题出在范围生成上。演示的示例代码:
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
输出
2 loops, best of 3: 2.2 s per loop
2 loops, best of 3: 3.46 µs per loop
测试的数量很低(我不希望它运行超过10分钟),但它已经表明范围(log(n,2))
比仅使用整数对数的对应项慢几个数量级。这真是令人惊讶,我不知道为什么会发生这种情况。可能是我电脑上的问题,可能是Sage问题或Python bug(我没有在Python上尝试过同样的问题)
使用xrange
而不是range
也没有帮助。此外,如果您使用.n()
获取数字,测试1将以2的相同速度运行
有人知道会发生什么吗?
谢谢 也许首先使用log(x,2)
(又称ld()
)不是一个好主意。我建议使用移位int值来实现ld()
:
这样,您就可以使用range()
和log()
来避免所有这些丑陋
在正常情况下,调用log()
>>> 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,但这应该是因为与循环初始化相比,循环体将占用大部分时间。Python2允许范围(一些浮点),但它已弃用,在Python3中不起作用
代码示例未提供指定的输出。但我们可以走过去。首先,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('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
这看起来像是一只鼠尾草虫
我创建了一个新笔记本并完成了以下操作:
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一样慢。但是,如果您以任何方式将其分解,请去掉范围,甚至添加m=n+0
,并使用m
而不是n
,它会下降到微秒
很明显,Sage试图在这里做一些复杂的事情,同时评估表达式,并感到困惑
要验证这一点,请在普通的旧ipython中:
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))'
正如你所料,它们都同样快
那…你怎么办
嗯,您可能想尝试追踪Sage bug并将其归档到上游。但与此同时,您可能希望在代码中找到一个变通方法
如上所述,只需执行m=n+0
并使用m
而不是n
似乎可以加快速度。看看这对你有用吗?天哪,我认识这个。它和我的一个朋友有关。首先,由于无聊的原因,使用Pythonint
而不是SageInteger
会带来额外的开销:
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
包装成整数(2)
)
然后就变得很奇怪了。为了给范围
产生一个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
听起来像是一个智者(也许是cython?)的问题。Pythonrange
甚至不接受浮动。Python在全局命名空间中也没有log
(如果不向timeit
添加setup
,就无法实现它)。而且n
对timeit
也不可用。而且timeit
上没有repeat
参数(我假设您是从timeit import timeit
获得的)。您的输出不是显示了timeit
返回的值是随机的吗?毕竟你试了两次同样的方法(n和k都是8),得到的结果差别很大。你真的预先计算了n吗?“另外,如果你用.n()
”得到数字。等等,什么?从哪里得到什么号码?好了,Sage是建立在ipython之上的,它所有的“神奇”语法都以%
或开头代码>。我愿意承认,在Python中编写一个正确的位移位循环要比调用log
慢得多。但无论我是对是错,你绝对不应该在不尝试测试的情况下断言它更快。也许你不应该假设我没有测试。在今天的处理器中,位移位并不比加法或其他简单的算术运算慢,这是我基本知识的一部分。但是我添加了一些测试来证明我的观点。好吧,你给苹果计时了
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