很多朋友都会疑问,自己写的代码为什么那么慢?
记得在读研究生的时候,我的导师到台湾去访学,跟我讲了他的亲身经历,说他写的算法,半个小时,计算机就出结果了,而且是在个人电脑上,台湾的学生2-3天都算不出来,问了之后,说还在计算。
导师说的问题是关于裂缝的应力分析的,简单讲,比如,一颗子弹打到玻璃上,玻璃会不会出现裂缝,裂缝周围的应力是怎么分布的,出现裂缝之后,玻璃还能不能继续达到使用的应力承载能力?
这是一个很复杂的计算,可以说导师的算法非常好。当然我们是做计算机数值计算的,对性能的追求可以说达到了极致,在应用领域,开发成本也是非常重要的考量。
我们公司的流体力学仿真软件一直非常追求性能,同时作为商业的软件开发公司,在开发成本上的考虑也是非常关注的。从计算性能,到可视化性能各个方面,都需要时间和经验的高度分析。
今天我们聊聊性能这件事。
关于性能和开发
1、编程之美的准则是什么?
美与丑,是做一个判别,既然是判别自然存在一个标准的问题,也就是准测。
作为算法工程师,首先自然是完成功能,然后才是性能。但是,功能这件事,通常很好描述,标准也相对清晰,而,性能这件事却无穷无尽。
一般情况下,我们的判别标准就是在功能满足客户需求的情况下,以当前市面上软件水平的客户体验效果为标准,别人一个操作等0.1秒,我们做到0.1秒以内就可以。
2、牺牲性能的背后,是组织形式的自由和更广泛的人才空间
这句话,说的有点冠冕堂皇,简而言之,三个方面:
(1)我们可以更方便的组织开发工作,让架构师和算法工程师可以有更多的工具,和选择方案。
(2)在较低的性能的要求下,开发人员的水平要求会降低很多,可以大大降低人员成本。
(3)更短的开发周期的需求,人好找,算法好组织,开发效率就更高。
Python性能测试
1、逻辑判断快还是基本运算快
import time
def get_time():
return time.time()
a=1
b=2
t0 = get_time()
for i in range(100000):
if a + b:
pass
t1 = get_time()
for i in range(100000):
c= a > b
t2 = get_time()
for i in range(100000):
if a > b:
pass
t3 = get_time()
print("add time:",t1-t0)
print("> time:",t2-t1)
print("if time:",t3-t2)
运行结果:
add time: 0.005983114242553711
“>” time: 0.008975982666015625
if time: 0.00628209114074707
可以看出判断语句,和基本运算速度是一个数量级的,如果1个循环,花费的时间是1e-8秒的量级,也就是1e-5毫秒。第二个循环中多了3e-8秒是因为赋值造成的。
2、调用python自带的max 和if语句判断
代码:
import time
def get_time():
return time.time()
def max1(a,b):
if a > b:
return a
else:
return b
a=1
b=2
c=0
t0 = get_time()
for i in range(100000):
if a > b:
c = a
else:
c = b
t1 = get_time()
for i in range(100000):
c = max(a, b)
t2 = get_time()
for i in range(100000):
c = max1(a,b)
t3 = get_time()
print("check time:",t1-t0,"rate:","1")
print("max time:",t2-t1,"rate",(t2-t1)/(t1-t0))
print("max1 time:",t3-t2,"rate",(t3-t2)/(t1-t0))
这里我们判断了,两个值比较大小。用了python自带的max函数,if判断,和将if判断放进一个函数max1.
我们看一下结果:
check time: 0.009987831115722656 rate: 1
max time: 0.019948720932006836 rate 1.9973025876062256
max1 time: 0.014958620071411133 rate 1.497684522104459
最快的,是直接if判断。我们把他作为比较的标准。自带的max速度是2倍,max1是1.5倍。所以,所以自己写的函数,性能上不见得比builtin自带的函数慢。我说的是在这里不必max慢,原因是max还有其他的判断的处理。
所以,只用自己用的东西,刚刚好,是最好的。但是这里,为什么要比较这种方式呢?显然max1的速度要慢于直接判断啊,这个还真不一定。后面我们慢慢看。
3、类的例子
我今天发了一个判断一个点是否在矩形区域内的类的微头条,也就是这个头条让我想试试看谁的性能更快。现在我们就检测一下!
主要是对比,使用距离函数判断和直接使用判断来辨别之间的性能上的差别。
- 使用max和abs定义的距离
- 使用if语句,替代max
- 全部使用if判断
代码比较长:
首先定义一个Rect类,
class Area1(Rect):
def __init__(self, x, y, w, h):
super().__init__( x, y, w, h)
@property
def center(self):
return self.x + self.w/2, self.y+self.h/2
def norm(self, pos=None):
"""
定义一个距离
"""
wph = self.w / self.h
if pos is not None:
norm0 = max(abs(pos[0] - self.center[0]), wph * abs(pos[1] - self.center[1]))
return norm0
else:
radius = max(abs(self.x - self.center[0]), wph * abs(self.y - self.center[1]))
return radius
我们定义的第一种情况,使用max和abs定义的距离。类名是Aera1。
我们class Area1(Rect):
def __init__(self, x, y, w, h):
super().__init__( x, y, w, h)
@property
def center(self):
return self.x + self.w/2, self.y+self.h/2
def norm(self, pos=None):
"""
定义一个距离
"""
wph = self.w / self.h
if pos is not None:
norm0 = max(abs(pos[0] - self.center[0]), wph * abs(pos[1] - self.center[1]))
return norm0
else:
radius = max(abs(self.x - self.center[0]), wph * abs(self.y - self.center[1]))
return radius
第二种情况,用if替代max的Aera2类
class Area2(Rect):
def __init__(self, x, y, w, h):
super().__init__(x, y, w, h)
@property
def center(self):
return self.x + self.w/2, self.y+self.h/2
def norm(self,pos):
"""
定义一个距离
"""
if abs(pos[0] - self.center[0]) > self.wph * abs(pos[1] - self.center[1]):
return abs(pos[0] - self.center[0])
return self.wph * abs(pos[1] - self.center[1])
def __contains__(self, pos):
pos_norm = self.norm(pos)
if pos_norm > self.radius:
return False
else:
return True
第三种,全部使用if判断Aera0类。
class Area0(Rect):
def __init__(self, x, y, w, h):
super().__init__( x, y, w, h)
@property
def center(self):
return self.x + self.w/2, self.y+self.h/2
def __contains__(self, pos):
if pos[0] < self.x:
return False
if pos[0] > self.x + self.w:
return False
if pos[1] < self.y:
return False
if pos[1] > self.y + self.h:
return False
return True
就代码组织来讲我更倾向于Area1这个类。大家可以认真看一下这几个代码的区别,我们就是用一些不同的操作来看看python代码在性能上的特点。
测试代码:
if __name__ == "__main__":
area0 =Area0(10,10,10,10)
area1 = Area1(10, 10, 10, 10)
area2= Area2(10, 10, 10, 10)
pos1 = (1, 2)
def check1():
for i in range(100000):
if pos1 in area1:
pass
t0 = time.time()
for i in range(100000):
if pos1 in area0:
pass
t1 = time.time()
for i in range(100000):
if pos1 in area1:
pass
t2 = time.time()
for i in range(100000):
if pos1 in area2:
pass
t3 = time.time()
check1()
t4 = time.time()
dt =t1-t0
print("Area0:",t1-t0,"rate:","1")
print("Area1:",t2-t1,"rate:",(t2-t1)/dt)
print("Area2:",t3-t2,"rate:",(t3-t2)/dt)
print("check1:",t4-t3,"rate:",(t4-t3)/dt)
看一下运行结果:
Area0: 0.017013072967529297 rate: 1
Area1: 0.23638176918029785 rate: 13.894125395891141
Area2: 0.12012887001037598 rate: 7.060974242551641
check1: 0.22240829467773438 rate: 13.072787914459486
同样以判断的情况为基准1。
发现判断确实是最快的,而且不是快一点点,也就是我觉得很好的一个算法,居然性能是傻傻的做判断慢了一个数量级。
为什么呢?因为我使用了python自带的函数,这个函数里面用到了更多的基本操作。
另外值得一说的是,这里check1只是对area1调用的简单的函数封装,但是速度上却有了提升。当然不是很大。至少说明,在复杂问题中,封到函数里,是有优势的。
结论和性能提升小技巧
- 赋值、判断、基本运算是同一量级的,我的电脑上都在亿分之一秒的速度量级,可以随意使用。
- 尽量把函数写成刚好需要的函数,不要多。这又是一个矛盾。
- 一些和循环无关的运算,尽量放在循环外面。
- 复杂过程,用函数封装并不会损失性能。
python的性能是一个非常复杂的问题,希望本文能给大家一些初步的感官,我们往往觉得很漂亮的算法和组织形式,其实是牺牲性能获得的。
跟很多程序员沟通的过程中,发现大家对性能的问题,大多都依赖语言本身,所以抱怨之声,大多会说某某语言如何。但是,要知道,例如AI这种事,再强大的语言都没办法替代算法。
算法会给软件带来无与伦比的提升,这是一个程序员的自我修养,也是环保精神,因为我们消耗的是计算资源。
有人说性能的考虑要占到70%,也有人认为性能并不重要,还是要选择优雅,你认为呢?
文源网络,仅供学习之用,侵删。
在学习Python的道路上肯定会遇见困难,别慌,我这里有一套学习资料,包含40+本电子书,800+个教学视频,涉及Python基础、爬虫、框架、数据分析、机器学习等,不怕你学不会! https://shimo.im/docs/JWCghr8prjCVCxxK/ 《Python学习资料》
关注公众号【Python圈子】,优质文章每日送达。
来源:oschina
链接:https://my.oschina.net/u/4477231/blog/4268349