同样的算法,为什么Python这么慢?PHP与Ruby都很快啊!

璀璨夜空 2018-09-12 10:54:18
================Python==============
#!/usr/bin/python

# PI = 4 * (1 / 1 - 1 / 3 + 1 / 5 - 1 / 7 + ...)

def pi(n):
i = 0
num = 1.0
sum = 0
while i < n:
sum += (-1)**i * (1.0 / num)
i += 1
num += 2
return sum * 4


NUM = 1000000
i = NUM
print("calculating...")
while i <= NUM + 10:
print("n: " + str(i) + " -> pi: " + str(pi(i)))
i += 1
print("done.")

===============结果===============
calculating...
n: 1000000 -> pi: 3.14159165359
n: 1000001 -> pi: 3.14159365359
n: 1000002 -> pi: 3.14159165359
n: 1000003 -> pi: 3.14159365359
n: 1000004 -> pi: 3.14159165359
n: 1000005 -> pi: 3.14159365358
n: 1000006 -> pi: 3.1415916536
n: 1000007 -> pi: 3.14159365358
n: 1000008 -> pi: 3.1415916536
n: 1000009 -> pi: 3.14159365358
n: 1000010 -> pi: 3.1415916536
done.
[Finished in 4.1s]

=============PHP=============
<?php

// PI = 4 * (1 / 1 - 1 / 3 + 1 / 5 - 1 / 7 + ...)

function math_pi($n)
{
$i = 0;
$num = 1.0;
$sum = 0;
while ($i < $n)
{
$sum += (-1) ** $i * (1.0 / $num);
$i += 1;
$num += 2;
}

return $sum * 4;
}

$NUM = 1000000;
$i = $NUM;
echo "calculating...\n";
while ($i <= $NUM + 10)
{
echo "n: " . $i . " -> pi: " . math_pi($i) . "\n";
$i++;
}
echo "done.\n";

==============结果===============
calculating...
n: 1000000 -> pi: 3.1415916535898
n: 1000001 -> pi: 3.1415936535888
n: 1000002 -> pi: 3.1415916535918
n: 1000003 -> pi: 3.1415936535868
n: 1000004 -> pi: 3.1415916535938
n: 1000005 -> pi: 3.1415936535848
n: 1000006 -> pi: 3.1415916535958
n: 1000007 -> pi: 3.1415936535828
n: 1000008 -> pi: 3.1415916535978
n: 1000009 -> pi: 3.1415936535808
n: 1000010 -> pi: 3.1415916535998
done.
[Finished in 1.2s]

===============Ruby=============
#!/usr/bin/ruby

# PI = 4 * (1 / 1 - 1 / 3 + 1 / 5 - 1 / 7 + ...)

def pi(n)
i = 0
num = 1.0
sum = 0
while i < n
sum += (-1) ** i * (1.0 / num)
i += 1
num += 2
end
sum * 4
end

puts "calculating..."
STDOUT.flush
NUM = 1_000_000
(NUM..NUM + 10).each do |n|
puts "n: #{n} -> pi: #{pi(n)}"
STDOUT.flush
end
puts "done."

==============结果=============
calculating...
n: 1000000 -> pi: 3.1415916535897743
n: 1000001 -> pi: 3.1415936535887745
n: 1000002 -> pi: 3.1415916535917745
n: 1000003 -> pi: 3.1415936535867743
n: 1000004 -> pi: 3.1415916535937742
n: 1000005 -> pi: 3.141593653584774
n: 1000006 -> pi: 3.141591653595774
n: 1000007 -> pi: 3.141593653582774
n: 1000008 -> pi: 3.1415916535977737
n: 1000009 -> pi: 3.141593653580774
n: 1000010 -> pi: 3.1415916535997734
done.
[Finished in 1.0s]
...全文
551 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
璀璨夜空 2018-10-02
  • 打赏
  • 举报
回复
我用的是MacBookPro
璀璨夜空 2018-09-17
  • 打赏
  • 举报
回复
谢谢你的耐心与仔细!分数都给你了!算法优化也许就是这个意思了,剔除所有不必要的,优化所有值得优化的。不过Python比PHP还有Ruby都慢应该就是事实了。
notback 2018-09-17
  • 打赏
  • 举报
回复
还是想你能帮我几种方法通过timeit跑跑,把结果给我看看,我主要想看看不同设备下对效率影响如何。
你的原始程序,我设备上timeit repeat=2,number=10 (跑2遍,每遍10次),每遍都在16秒左右。你丫用啥电脑的,能跑到4秒。

python 循环效率低,这是不争的事实,但是python很多问题可以通过列表特性减少循环,代码量也比较少。
目前纠结程序效率并没有太大意义,绝大多数代码并不牵涉效率问题。关键是代码量和易写度,以及整个生态圈,使用范围等。
也可能是我个人因为练习盲打偷懒的缘故(少练了一行数字键,也就是少了一行符号键盲打),对于符号多的代码,天生不感冒。
:)
notback 2018-09-16
  • 打赏
  • 举报
回复
引用 4 楼 wangxinghe 的回复:
如果用list,那么下面这个就更慢了!

def pi(n):
list = [(-1) ** (x + 1) * (1.0 / (x * 2 - 1)) for x in range(1, n)]
return sum(list) * 4



代码不是写给你了吗。我的代码是你代码效率的约10倍。

===========准备折腾的分割线========================
姑且称你一楼的代码为 代码A,4楼代码为代码B
首先,先说几个不怎么关键的东西
代码A中,1.0这东西不怎么必要,直接写1好了,对效率有非常小的影响。
代码B中,变量名不要用list,不怎么好。
其次,再强调一遍,我的代码是你代码A效率的10倍。
最后,你可能觉得我的代码不够通用,想尽量与代码A的逻辑一致,所以搞出代码B。那我们直接在代码B上分析和提高你的代码效率。

第一个测试1.0 改为1 ,代码效率提高约6%,由于这个1.0完全没有必要,所以,以后代码中已经完全去除1.0直接使用1,代码命名为
代码B改,以下测试基于代码B改。

list1 = [(-1) ** (x + 1) * (1 / (x * 2 - 1)) for x in range(1, n+1)]

第二个直觉 python 中 ** 慢,所以 import math , 用math.pow 代替 ** ,代码效率在代码B改基础上提高约28%

mp = math.pow
list1 = [mp((-1) , (x + 1)) * (1 / (x * 2 - 1)) for x in range(1, n+1)]

第三步,发觉你这个算法转换逻辑有问题,前面的(-1)**(x+1)只是为了获取正负号,但是做了N步的幂运算。效率与第二步一致。

list1 = [mp((-1), ((x + 1) % 2)) * (1 / (x * 2 - 1)) for x in range(1, n + 1)]

第四步,由于改动算法逻辑,无需做很多幂运算,使用**代替math.pow,代码效率在代码B改的基础上提高35%

list1 = [(-1) ** ((x + 1) % 2) * (1 / (x * 2 - 1)) for x in range(1, n+1)]

中期结论:
完全按照数学逻辑的代码修改基本没什么优化的地方,效率最高提高35%。就是说按照第四步代码,你的运行时间该在2.6秒左右。
python中运算符中的**的幂运算无优化。math.pow 对于幂运算有优化。

=====================生命不息,折腾不止的分割线=============================

第五步,对于这道题的算法,用幂运算只能算是一种纯数学算法,python中有个东西叫三元运算符。
格式如下:truevalue if expression else falsevalue
这个算法中,其实只是要正负号,而非幂运算,更改代码用三元运算,效率在代码B改基础上提高64%

list1 = [(1 if x % 2 else -1) / (x * 2 - 1) for x in range(1, n + 1)]

第六步,既然range能直接生成所有的奇数,干嘛要计算?直接用range(1,n*2,2)生成所有奇数,结合三元运算,效率在代码B改基础上提高67%

list1 = [(1 if (x + 1) / 2 % 2 else -1) / x for x in range(1, n * 2, 2)]

第七步,将代码A改为 % 模式,效率在代码B改基础上提高42%,改为 三元运算模式,效率在代码B基础上提高59%。足见列表推导式还是有效率提升的。
====================累死的分割线=======================
结合我给出的最初效率最高的代码,得出最终结论:
1 python 循环效率低,用列表推导式可以提高部分效率。
2 幂运算x的y次方,y较小的时候**更有效率(导入math库,用pow增加导入模块开销),y很大,math.pow优化更好。
3 效率排名:一般运算 >三元运算 > 幂运算

==================真的结束了的分割线====================================
由于我们之间的设备存在差异性,我也不知道我的优化结果对你来说有多大的区别,看在我这么辛苦折腾的基础上,如果能反馈一下相关代码的
优化结果,也不枉我辛苦一场。
可以把代码写成几个函数,使用timeit测试

import timeit
print("time test")
NUM = 100000
t1 = timeit.Timer(stmt="pi(NUM)", setup="from __main__ import pi", globals=globals())
t2 = timeit.Timer(stmt="pii(NUM)", setup="from __main__ import pi", globals=globals())
t3 = timeit.Timer(stmt="ppi(NUM)", setup="from __main__ import pi", globals=globals())

print(f"pi = {t1.repeat(repeat=3,number=10)}")
print(f"pii = {t2.repeat(repeat=3,number=10)}")
print(f"ppi = {t3.repeat(repeat=3,number=10)}")
璀璨夜空 2018-09-15
  • 打赏
  • 举报
回复
引用 3 楼 NotBack 的回复:
小样,电脑不错啊,我笔记本跑你的程序得17s

def pi(n):
y = n * 2
list1 = [1 / x for x in range(1, y, 4)]
list2 = [-1 / x for x in range(3, y, 4)]
return (sum(list1) + sum(list2)) * 4

试试这个


如果用list,那么下面这个就更慢了!

def pi(n):
list = [(-1) ** (x + 1) * (1.0 / (x * 2 - 1)) for x in range(1, n)]
return sum(list) * 4

notback 2018-09-14
  • 打赏
  • 举报
回复
小样,电脑不错啊,我笔记本跑你的程序得17s

def pi(n):
y = n * 2
list1 = [1 / x for x in range(1, y, 4)]
list2 = [-1 / x for x in range(3, y, 4)]
return (sum(list1) + sum(list2)) * 4

试试这个
notback 2018-09-14
  • 打赏
  • 举报
回复
python 循环效率比较低,特别这种非常多次的循环。
楼主可以考虑使用List的特性,以及列表推导式来进行计算。实在不行先引入个numpy试试里面的函数。
oyljerry 2018-09-12
  • 打赏
  • 举报
回复
其他的可能利用了C

37,720

社区成员

发帖
与我相关
我的任务
社区描述
JavaScript,VBScript,AngleScript,ActionScript,Shell,Perl,Ruby,Lua,Tcl,Scala,MaxScript 等脚本语言交流。
社区管理员
  • 脚本语言(Perl/Python)社区
  • IT.BOB
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧