Python测试:小心藏在简洁背后的那些陷阱

隐身守侯 提交于 2020-05-06 15:33:41

很多朋友都会疑问,自己写的代码为什么那么慢?

记得在读研究生的时候,我的导师到台湾去访学,跟我讲了他的亲身经历,说他写的算法,半个小时,计算机就出结果了,而且是在个人电脑上,台湾的学生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圈子】,优质文章每日送达。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!