下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922
论坛 >编程语言 >Python程序员必知必会的开发者工具 (2)

Python程序员必知必会的开发者工具 (2)

spring发布于 2018-01-16 09:49查看:1144回复:1

程序分析

profile模块和cProfile模块可以用来分析程序。它们的工作原理都一样,唯一的区别是,cProfile模块是以C扩展的方式实现的,如此一来运行的速度也快了很多,也显得比较流行。这两个模块都可以用来收集覆盖信息(比如,有多少函数被执行了),也能够收集性能数据。对一个程序进行分析的最简单的方法就是运行这个命令:

image.png

此外,也可以使用profile模块中的run函数:


image.png

该函数会使用exec语句执行command中的内容。filename是可选的文件保存名,如果没有filename的话,该命令的输出会直接发送到标准输出上。

下面是分析器执行完成时的输出报告:

image.png


当输出中的第一列包含了两个数字时(比如,121/1),后者是元调用(primitive call)的次数,前者是实际调用的次数(译者注:只有在递归情况下,实际调用的次数才会大于元调用的次数,其他情况下两者都相等)。对于绝大部分的应用程序来讲使用该模块所产生的的分析报告就已经足够了,比如,你只是想简单地看一下你的程序花费了多少时间。然后,如果你还想将这些数据保存下来,并在将来对其进行分析,你可以使用pstats模块。

假设你想知道你的程序究竟在哪里花费了多少时间。

如果你只是想简单地给你的整个程序计时的话,使用Unix中的time命令就已经完全能够应付了。例如:

image.png

通常来讲,分析代码的程度会介于这两个极端之间。比如,你可能已经知道你的代码会在一些特定的函数中花的时间特别多。针对这类特定函数的分析,我们可以使用修饰器decorator,例如:

image.png

使用decorator的方式很简单,你只需要把它放在你想要分析的函数的定义前面就可以了。例如:

image.png

如果想要分析一个语句块的话,你可以定义一个上下文管理器(context manager)。例如:

image.png

接下来是如何使用上下文管理器的例子:

image.png

如果想研究一小段代码的性能的话,timeit模块会非常有用。例如:

image.png

timeit的工作原理是,将第一个参数中的语句执行100万次,然后计算所花费的时间。第二个参数指定了一些测试之前需要做的环境准备工作。如果你需要改变迭代的次数,可以附加一个number参数,就像这样:

image.png

当进行性能评估的时候,要牢记任何得出的结果只是一个估算值。函数time.perf_counter()能够在任一平台提供最高精度的计时器。然而,它也只是记录了自然时间,记录自然时间会被很多其他因素影响,比如,计算机的负载。如果你对处理时间而非自然时间感兴趣的话,你可以使用time.process_time()。例如:

image.png

最后也是相当重要的就是,如果你想做一个详细的性能评估的话,你最好查阅timetimeit以及其他相关模块的文档,这样你才能够对平台相关的不同之处有所了解。

profile模块中最基础的东西就是run()函数了。该函数会把一个语句字符串作为参数,然后在执行语句时生成所花费的时间报告。

image.png

性能优化

当你的程序运行地很慢的时候,你就会想去提升它的运行速度,但是你又不想去借用一些复杂方案的帮助,比如使用C扩展或是just-in-time(JIT)编译器。

那么这时候应该怎么办呢?要牢记性能优化的第一要义就是“不要为了优化而去优化,应该在我们开始写代码之前就想好应该怎样编写高性能的代码”。第二要义就是“优化一定要抓住重点,找到程序中最重要的地方去优化,而不要去优化那些不重要的部分”。

通常来讲,你会发现你的程序在某些热点上花费了很多时间,比如内部数据的循环处理。一旦你发现了问题所在,你就可以对症下药,让你的程序更快地执行。

 使用函数

许多开发者刚开始的时候会将Python作为一个编写简单脚本的工具。当编写脚本的时候,很容易就会写一些没有结构的代码出来。例如:

image.png

但是,却很少有人知道,定义在全局范围内的代码要比定义在函数中的代码执行地慢。他们之间速度的差别是因为局部变量与全局变量不同的实现所引起的(局部变量的操作要比全局变量来得快)。所以,如果你想要让程序更快地运行,那么你可以简单地将代码放在一个函数中,就像这样:

image.png

这样操作以后,处理速度会有提升,但是这个提升的程度依赖于程序的复杂性。根据经验来讲,通常都会提升15%到30%之间。

选择性地减少属性的访问

当使用点(.)操作符去访问属性时都会带来一定的消耗。本质上来讲,这会触发一些特殊方法的执行,比如__getattribute__()__getattr__(),这通常都会导致去内存中字典数据的查询。

你可以通过两种方式来避免属性的访问,第一种是使用from module import name的方式。第二种是将对象的方法名保存下来,在调用时直接使用。为了解释地更加清楚,我们来看一个例子:

image.png

上面的代码在我的计算机上运行大概需要40秒的时间。现在我们把上面代码中的compute_roots()函数改写一下:

image.png

这个版本的代码执行一下大概需要29秒。这两个版本的代码唯一的不同之处在于后面一个版本减少了对属性的访问。在后面一段代码中,我们使用了sqrt()方法,而非math.sqrt()result.append()函数也被存进了一个局部变量result_append中,然后在循环当中重复使用。

然而,有必要强调一点是说,这种方式的优化仅仅针对经常运行的代码有效,比如循环。由此可见,优化仅仅在那些小心挑选出来的地方才会真正得到体现。

理解变量的局部性

上面已经讲过,局部变量的操作比全局变量来得快。对于经常要访问的变量来说,最好把他们保存成局部变量。例如,考虑刚才已经讨论过的compute_roots()函数修改版:

image.png

在这个版本中,sqrt函数被一个局部变量所替代。如果你执行这段代码的话,大概需要25秒就执行完了(前一个版本需要29秒)。 这次速度的提升是因为sqrt局部变量的查询比sqrt函数的全局查询来得稍快。

局部性原来同样适用于类的参数。通常来讲,使用self.name要比直接访问局部变量来得慢。在内部循环中,我们可以将经常要访问的属性保存为一个局部变量。例如:

image.png

避免不必要的抽象

任何时候当你想给你的代码添加其他处理逻辑,比如添加装饰器,属性或是描述符,你都是在拖慢你的程序。例如,考虑这样一个类:image.png

现在,让我们简单地测试一下:

image.png

正如你所看到的,我们访问属性y比访问简单属性x不是慢了一点点,整整慢了4.5倍之多。如果你在乎性能的话,你就很有必要问一下你自己,对y的那些额外的定义是否都是必要的了。如果不是的话,那么你应该把那些额外的定义删掉,用一个简单的属性就够了。如果只是因为在其他语言里面经常使用getter和setter函数的话,你完全没有必要在Python中也使用相同的编码风格。

使用内置的容器

内置的数据结构,例如字符串(string),元组(tuple),列表(list),集合(set)以及字典(dict)都是用C语言实现的,正是因为采用了C来实现,所以它们的性能表现也很好。如果你倾向于使用你自己的数据结构作为替代的话(例如,链表,平衡树或是其他数据结构),想达到内置数据结构的速度的话是非常困难的。因此,你应该尽可能地使用内置的数据结构。

避免不必要的数据结构或是数据拷贝

有时候程序员会有点儿走神,在不该用到数据结构的地方去用数据结构。例如,有人可能会写这样的的代码:

image.png

也许他这么写是为了先得到一个列表,然后再在这个列表上进行一些操作。但是第一个列表是完全没有必要写在这里的。我们可以简单地把代码写成这样就行了:

image.png

有鉴于此,你要小心那些偏执程序员所写的代码了,这些程序员对Python的值共享机制非常偏执。函数copy.deepcopy()的滥用也许是一个信号,表明该代码是由菜鸟或者是不相信Python内存模型的人所编写的。在这样的代码里,减少copy的使用也许会比较安全。

在优化之前,很有必要先详细了解一下你所要使用的算法。如果你能够将算法的复杂度从O(n^2)降为O(n log n)的话,程序的性能将得到极大的提高。

如果你已经打算进行优化工作了,那就很有必要全局地考虑一下。普适的原则就是,不要想去优化程序的每一个部分,这是因为优化工作会让代码变得晦涩难懂。相反,你应该把注意力集中在已知的性能瓶颈处,例如内部循环。

你需要谨慎地对待微优化(micro-optimization)的结果。例如,考虑下面两种创建字典结构的方式:

image.png

后面那一种方式打字打的更少一些(因为你不必将key的名字用双引号括起来)。然而当你将这两种编码方式进行性能对比时,你会发现使用dict()函数的方式比另一种慢了3倍之多!知道了这一点以后,你也许会倾向于扫描你的代码,把任何出现dict()的地方替换为另一种冗余的写法。然而,一个聪明的程序员绝对不会这么做,他只会将注意力放在值得关注的地方,比如在循环上。在其他地方,速度的差异并不是最重要的。但是,如果你想让你的程序性能有质的飞跃的话,你可以去研究下基于JIT技术的工具。比如,PyPy项目,该项目是Python解释器的另一种实现,它能够分析程序的执行并为经常执行的代码生成机器码,有时它甚至能够让Python程序的速度提升一个数量级,达到(甚至超过)C语言编写的代码的速度。但是不幸的是,在本文正在写的时候,PyPy还没有完全支持Python 3。所以,我们还是在将来再来看它到底会发展的怎么样。基于JIT技术的还有Numba项目。该项目实现的是一个动态的编译器,你可以将你想要优化的Python函数以注解的方式进行标记,然后这些代码就会在LLVM的帮助下被编译成机器码。该项目也能够带来极大的性能上的提升。然而,就像PyPy一样,该项目对Python 3的支持还只是实验性的。


收藏(0)0
查看评分情况

全部评分

此主贴暂时没有点赞评分

总计:0

回复分享

版主推荐

    共有1条评论

    • 高端大气上档次,低调奢华有内涵

      2018-01-17 09:33赞 (0)回复沙发
    • IT宅男
    • mr jack
    • Mr ken
    • Mright
    • cappuccino
    • YUI
    • 课课家运营团队
    • 课课家技术团队1
    • 酸酸~甜甜
    • 选择版块:

    • 标题:

    • 内容

    • 验证码:

    • 标题:

    • 内容

    • 选择版块:

    移动帖子x

    移动到: