- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我在 Python 中有两种寻找素数的算法。每一个的内循环看起来都执行了相同的次数,同样简单。然而,其中一个比另一个花费了 10 倍。我的问题是:
Why? Is this some quirk of Python that can be optimized away (how?), or am I missing something else?
我正在解决的问题本质上来自http://www.spoj.pl/problems/PRIME1/ .在我的例子中,我有 N = 10 ** 9,delta = 10 ** 5,我想找到 N-delta 和 delta 之间的所有素数。我还有 smallprimes
,这是一个预制列表,包含所有小于或等于 N 的平方根的素数。第一个算法非常简单:
def range_f1(lo, hi, smallprimes):
"""Finds all primes p with lo <= p <= hi.
smallprimes is the sorted list of all primes up to (at least) square root of hi.
hi & lo might be large, but hi-lo+1 miust fit into a long."""
primes =[]
for i in xrange(hi-lo+1):
n = lo + i
isprime = True
for p in smallprimes:
if n % p == 0:
isprime = False
break
if isprime:
primes.append(n)
return primes
调用 range_f1(N-delta,N,smallprimes)
需要很长时间(大约 10 秒)。内循环调用了 195170 次。我也有这个算法的一个版本,它用列表理解替换循环(这是我实际用于分析的那个;见问题的结尾)但这并不快。
第二个版本是埃拉托色尼筛法的(一个丑陋的实现):
def range_sieve(lo, hi, smallprimes):
"""Parameters as before"""
# So ugly! But SO FAST!! How??
delta = hi-lo+1
iamprime = [True] * delta # iamprime[i] says whether lo + i is prime
if lo<= 1:
iamprime[1 - lo] = False
def sillyfun(): # For profiling & speed comparison
pass
for p in smallprimes:
rem = lo % p
if rem == 0:
iamprime[0] = False
for i in xrange(p - rem, delta, p):
iamprime[i] = False
sillyfun()
if p >= lo and p <= hi:
iamprime[p - lo] = True
return [p + lo for (p, ami) in enumerate(iamprime) if ami]
这大约快了 10 倍,用时不到 2 秒。但是,内部循环 (sillyfun()) 被调用了 259982 次,比最后一个案例多。我无法解释为什么这么快。
我想可能是因为第一个算法的内循环包含模运算,而第二个只有一个赋值。然而,以下似乎暗示赋值并不比模运算快:
>>> from timeit import timeit
>>> timeit("10**9 % 2341234")
0.23445186469234613
>>> timeit("a[5000]=False", "a = [True] * 10000")
0.47924750212666822
这是我实际使用的第一个算法的(可读性较差)版本:
def range_f2(lo, hi, smallprimes):
primes =[]
for i in xrange(hi-lo+1):
n = lo + i
try:
(1 for p in smallprimes if n % p ==0).next()
except StopIteration:
primes.append(n)
return primes
以下是为 range_f2() 调用探查器的结果(注意计算生成表达式的时间数):
>>> from cProfile import run as prof
>>> prof("range_f2(N-delta,N,sp)")
200005 function calls in 13.866 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 13.866 13.866 <string>:1(<module>)
195170 12.632 0.000 12.632 0.000 prime1.py:101(<genexpr>)
1 1.224 1.224 13.865 13.865 prime1.py:90(range_f2)
4832 0.009 0.000 0.009 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
这是 range_sieve() 的分析器结果:
>>> prof("range_sieve(N-delta,N,sp)")
259985 function calls in 1.370 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.003 0.003 1.370 1.370 <string>:1(<module>)
1 0.877 0.877 1.367 1.367 prime1.py:39(range_sieve)
259982 0.490 0.000 0.490 0.000 prime1.py:51(sillyfun)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
最后,这是他生成小素数列表的完整代码(以一种非常愚蠢的方式),以便您可以检查得到的结果:http://pastebin.com/7sfN4mG4
更新 根据大众需求,第一段代码的分析数据。没有关于内部循环执行了多少次的数据,但很明显它与第三次相同。
>>> prof("range_f1(N-delta,N,sp)")
4835 function calls in 14.184 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 14.184 14.184 <string>:1(<module>)
1 14.162 14.162 14.183 14.183 prime1.py:69(range_f1)
4832 0.021 0.000 0.021 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
最佳答案
区别在于算法。在第一个版本中,trial division,你针对所有小素数测试每个候选 - 当小素数超过 candidate ** 0.5
时你不会停止对于 range( 10**9 - 10**5 ,10**9)
如果 smallprimes 有一个很好的上限,但如果范围的长度相对于上限大得多。对于复合 Material ,这不会产生太多成本,因为它们中的大多数至少有一个小素因数。但是对于素数,你必须一路走到 N**0.5
。那个区间内大概有10**5/log(10**9)
个素数,每一个素数都被试分为10**4.5/log(10**4.5 )
素数,因此对素数进行了大约 1.47*10**7
次试验。
另一方面,使用筛子,您只能划掉组合,每个组合被划掉的次数与它有素数的次数一样多,而素数根本不会划掉(因此素数是免费的)。 n
的质因数的数量受限于(的倍数)log(n)
(这是一个粗略的上限,通常大大高估),因此给出了上限10**5*log(10**9)
(乘以一个小常数)交叉的边界,大约 2*10**6
。除此之外,交叉可能比除法更少工作(不知道 Python,它用于 C 数组)。所以你用筛子做的工作更少。
编辑:收集了 10**9-10**5
到 10**9
的实际数字。
Ticks: 259987
4832
Divisions: 20353799
4832
筛子只进行了 259987 次交叉,您会看到上面的粗略上限高估了一个很大的因素。试验除法需要超过 2000 万个除法,其中 16433632 个用于素数(x/log x
低估了素数的数量,对于 x = 10**4.5
大约低估了 10 %), 3435183 用于该范围内最小质因数大于n**(1/3)
的3297个数。
关于python - 为什么两种寻找素数的算法在速度上相差如此之大,即使它们看起来迭代次数相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8640934/
我使用此代码来测量我的 C 程序的计算时间: clock_t start = clock() ; do_some_work() ; clock_t end = clock() ; double ela
我正在尝试找到一个 SQL 查询,该查询将计算至少相差 30 分钟的不同开始时间的数量。 我有一些员工在一周内至少三个不同的时间开始工作时获得积分,其中开始时间与其他开始时间至少相差 30 分钟。 例
我正在尝试显示一个时间表,并且我想在一周内的每组比赛之后创建一个休息时间。例如,我想要一个 在第一周的前五场比赛之后。然后在第二周的五场比赛之后再次进行。我现在所拥有的包括前六场比赛,然后是之后的每五
float a=67107842,b=512; float c=a/b; printf("%lf\n",c); 为什么 c 是 131070.000000 而不是正确的值 131070.0039062
我有一个谓词 predicate = [NSPredicate predicateWithFormat:@"character.id IN %@", indexs]; 它生成以下 SQL: CoreD
我正在编写一个在 UI 中有一个 DatePicker 和一个 TimePicker 的应用程序。我需要获取用户设置的日期和时间并存储在服务器中。 例如用户选择“2015 年 11 月 13 日 13
我有一个带有 OffsetDateTime 的 JPA 实体类像这样的字段: @Entity class MyEntity(clock: Clock) { // ... val cre
这个问题已经有答案了: What does size of the memcmp return value mean? (2 个回答) 已关闭 3 年前。 所以我必须使用 C 重新创建 memcmp(
两个查询。第一个比第二个长 200 倍。为什么?PostgreSQL 10.1。 Metro 和 Sel - 同一张 table 上的 View 。 EXPLAIN ANALYZE SELECT *
我在 Google Play 中有一个应用,并在其上进行 Firebase 分析。我正在尝试跟踪广告来源。我不明白正确的下载次数在哪里,因为 Google Play Console 显示 150 个安
我正在使用 Python 进行核密度估计,并使用高斯混合模型对多维数据样本的可能性进行排序。每条数据都是一个角度,我不确定如何处理机器学习角度数据的周期性。 首先,我通过添加 360 来移除所有负角,
最近我遇到了一件非常奇怪的事情——一种方法在性能分析器下非常慢,没有明显的原因。它包含很少的 long 操作,但被调用得相当频繁 - 它的总体使用量约为总程序时间的 30-40%,而其他部分似乎“更重
请有人向我解释这种情况。 我有以下代码: Click the button to display the date and time as a string, using the ISO standa
如何在考虑时间复杂度的情况下找到从 1 到 20 亿(使用任何编程语言且不使用任何外部库)相差 6 的连续质数对的数量,例如 (23,29)? 尝试过埃拉托色尼筛法,但获得连续素数是一项挑战 使用了生
我正在尝试找到一种方法来过滤两个日期/时间字段的差异小于 90 分钟的记录。 例子: orders.created_at = 2015-08-09 20:30:20 table2.created_at
我在使用 EEPlus 库从 excel (.xlsx) 文件获取正确的日期字段值时遇到问题。 具体问题是在 excel 中我有例如1900.01.04,但在 C# 中我得到 1900.01.03。
我是一名优秀的程序员,十分优秀!