我一直在使用来自项目Euler的problem 7,我注意到我的两个主要发现方法非常相似,但运行速度非常不同。
#!/usr/bin/env python3
import timeit
def lazySieve (num_primes):
if num_primes == 0: return []
primes = [2]
test = 3
while len(primes) < num_primes:
sqrt_test = sqrt(test)
if all(test % p != 0 for p in primes[1:]): # I figured this would be faster
primes.append(test)
test += 2
return primes
def betterLazySieve (num_primes):
if num_primes == 0: return []
primes = [2]
test = 3
while len(primes) < num_primes:
for p in primes[1:]: # and this would be slower
if test % p == 0: break
else:
primes.append(test)
test += 2
return primes
if __name__ == "__main__":
ls_time = timeit.repeat("lazySieve(10001)",
setup="from __main__ import lazySieve",
repeat=10,
number=1)
bls_time = timeit.repeat("betterLazySieve(10001)",
setup="from __main__ import betterLazySieve",
repeat=10,
number=1)
print("lazySieve runtime: {}".format(min(ls_time)))
print("betterLazySieve runtime: {}".format(min(bls_time)))
使用以下输出运行:
lazySieve runtime: 4.931611961917952
betterLazySieve runtime: 3.7906006319681183
与this问题不同,我不仅仅想要任何/所有的返回值。
来自all()
的回报是否如此缓慢以至于在所有情况下覆盖它的使用情况? for-else
中断是否比短路的all()更快?
您怎么看?
修改:在Reblochon Masque建议的平方根循环终止检查中添加
更新: ShadowRanger的answer是正确的。
更改后
all(test % p != 0 for p in primes[1:])
到
all(map(test.__mod__, primes[1:]))
我在运行时记录了以下减少:
lazySieve runtime: 3.5917471940629184
betterLazySieve runtime: 3.7998314710566774
修改:删除了Reblochon的速度以保持问题清晰。对不起男士。
答案 0 :(得分:1)
我可能错了,但我认为每次它在生成器表达式中计算test % p != 0
时,它都会在新的堆栈帧中执行,因此调用函数会产生类似的开销。您可以在回溯中看到堆栈框架的证据,例如:
>>> all(n/n for n in [0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
ZeroDivisionError: integer division or modulo by zero
答案 1 :(得分:1)
这是几个问题的组合:
all
生成器表达式中的每次迭代都需要查找test
以确保它没有“{1}}成本。 t改变了,它是通过dict
查找(其中局部变量查找是固定大小数组中的廉价查找)来实现的。all
在CPython的C层实现)您可以采取哪些措施来尽量减少差异或消除差异:
test
拉入生成器的本地范围,例如作为一个愚蠢的黑客all(test % p != 0 for test in (test,) for p in primes[1:])
map
删除进程中的所有字节码执行,例如all(map(test.__mod__, primes[1:]))
(通过预先查询test.__mod__
而不是每次循环一次,也可以达到#2)如果输入足够大,#3可以有时赢得原始代码,至少在Python 3.5上(我在ipython中进行微基准测试),取决于许多因素。它并不总是赢,因为BINARY_MODULO
的字节码解释器中有一些优化适用于可以直接跳到int.__mod__
代码旁路的CPU寄存器的值,但它通常会执行非常相似。
答案 2 :(得分:0)
对于一个令人费解的结果,这是一个有趣的问题,遗憾的是我没有明确的答案......也许是因为样本量或这个计算的细节?但是和你一样,我发现它令人惊讶。
但是,可以使lazysieve
比betterlazysieve
更快:
def lazySieve (num_primes):
if num_primes == 0:
return []
primes = [2]
test = 3
while len(primes) < num_primes:
if all(test % p for p in primes[1:] if p <= sqr_test):
primes.append(test)
test += 2
sqr_test = test ** 0.5
return primes
它在您的版本的大约65%的时间内运行,并且比我的系统上的betterlazysieve
快15%。
在jupyter notebook w python 3.4.4中使用%%timit
在一个古老的macbook air上:
%%timeit
lazySieve(10001)
# 1 loop, best of 3: 8.19 s per loop
%%timeit
betterLazySieve(10001)
# 1 loop, best of 3: 10.2 s per loop