《Python高性能编程》——2.4 计时的简单方法——打印和修饰

简介:

本节书摘来自异步社区《Python高性能编程》一书中的第2章,第2.4节,作者[美] 戈雷利克 (Micha Gorelick),胡世杰,徐旭彬 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.4 计时的简单方法——打印和修饰

运行例2-4,我们看到的输出是由代码中几句print语句生成的。在Ian的笔记本上用CPython 2.7跑这段代码要花大约12秒。运行时间一般都会有一些波动。你必须在计时的同时观察这些正常的变化,否则你可能会误把某个随机的运行时间的变化当作是由于某次代码的改进造成的。

你的计算机在运行你的代码时还会进行其他任务,比如访问网络、磁盘或RAM,这些因素都会导致程序运行时间的变化。

Ian的笔记本是一台Dell E6420,拥有一个Intel Core I7-2720QM的CPU(2.20GHz,6MB缓存,4核)以及8GB的RAM,操作系统是Ubuntu 13.10。

在calc_pure_python中(例2-2),我们能看到一些print语句。这是最简单的在函数内部测量一段代码执行时间的方法。这个基本方法既快且脏,是在你刚开始着手调查代码时非常有用的手段。

在代码除错和性能分析上使用print语句是常用的手段。虽然它很快就会变得无法管理,但适用于简短的调查。用完它们以后要记得收拾干净,否则它们会搞乱你的stdout。

一个稍微干净一点的方法是使用修饰器——在需要调查的函数上面增加一行代码。我们的修饰器十分简单,仅仅复制了print语句的功能。后面我们会让它变得更加高级。

在例2-5中,我们定义了一个新函数timefn,它以一个函数fn为参数。它的内嵌函数measure_time接受args(数量可变的位置参数)以及*kwargs(数量可变的键值对参数)等参数并将其传入fn执行。在执行fn前后,我们抓取time.time()并将结果和fn.func_name一起打印出来。使用这个修饰器的开销很小,但如果你调用上千万次fn,开销就会变得引人注意。我们用@wraps(fn)将函数名和docstring暴露给fn的调用者(否则调用者看到的将是修饰器自身的函数名和docstring,而不是被修饰的函数的)。

例2-5 定义一个修饰器来自动测量时间

from functools import wraps

def timefn(fn):
    @wraps(fn)
    def measure_time(*args, **kwargs):
        t1 = time.time()
        result = fn(*args, **kwargs)
        t2 = time.time()
        print ("@timefn:" + fn.func_name + " took " + str(t2 - t1) + " seconds")
        return result
    return measure_time

@timefn
def calculate_z_serial_purepython(maxiter, zs, cs):
    ...

当我们运行这个版本的代码时(之前的print语句依然保留),我们会看到修饰器打印的执行时间要略快于calc_pure_python打印的时间。这是由于函数的调用带来了额外开销(差异非常小):

Length of x: 1000
Total elements: 1000000
@timefn:calculate_z_serial_purepython took 12.2218790054 seconds
calculate_z_serial_purepython took 12.2219250043 seconds
 备忘 

额外的分析信息不可避免地降低了代码的执行速度——某些性能分析选项非常详细以至于带来了巨大的性能代价。分析信息的细节程度和运行速度是你必须要进行权衡的。

我们可以用timeit模块作为另一种测量执行速度的方法。通常来说,你会在解决问题的过程中用它来为各种简单的语句计时。

 警告  

注意,timeit模块暂时禁用了垃圾收集器。如果你的操作会调用到垃圾收集器,那么它有可能影响到你实际操作的速度。更多信息请参见Python文档:http://bit.ly/timeit_doc。

你可以从命令行运行timeit如下:

$ python -m timeit -n 5 -r 5 -s "import julia1"
   "julia1.calc_pure_python(desired_width=1000,
   max_iterations=300)"

注意你必须以-s命令在设置阶段导入julia1模块,因为calc_pure_python来自那个模块。timeit有一些合理的默认值适用于一段简短的代码,但对于要长期运行的代码来说,最好指定循环次数(-n 5)以及重复次数(-r 5)。timeit会对语句循环执行n次并计算平均值作为一个结果,重复r次并选出最好的那个结果。

如果我们不指定-n和-r运行timeit,默认是循环10次重复5次,需要6分钟。改变默认值可以让你更快获得结果。

我们只关注最好的那个结果,平均值以及最差结果可能是由于其他进程的影响。选择循环5次重复5次应该能给我们一个较为公正的结果:

5 loops, best of 5: 13.1 sec per loop

试着多运行几次,看看我们会不会得到不同的结果——你可能需要更多的重复次数来获得一个稳定的最佳结果时间。在这一点上不存在“正确”的配置,所以如果你发现你的计时结果变动范围很广,你就要选择更高的重复次数,直到获得稳定的最终结果。

我们的结果显示调用calc_pure_python的总体开销是13.1秒(最佳情况),而@timefn修饰器测算的单次调用calculate_z_serial_purepython耗时12.2秒。中间的差别主要是用于创建zs和cs列表的时间。

在IPython内部,我们可以用同样的方式使用%timeit魔法函数。如果你在IPython中用互动的方式开发你的代码且函数在本地名字空间(可能是因为你正在使用%run),那么你可以用:

%timeit calc_pure_python(desired_width=1000, max_iterations=300)

还有一点值得考虑的是一台计算机上的负载变化。很多后台运行的任务(如Dropbox、备份等)都会随机影响CPU和磁盘资源。网页上的脚本也会导致不可预测的资源使用。图2-4显示了我们刚刚进行的计时过程中某个CPU的使用率达到了100%,机器中的其他核心都在轻松处理其他的任务。


screenshot

系统监视器会时不时地显示这台机器上的活动峰值。有必要检查会不会有其他事件发生影响了你的关键资源(CPU、磁盘、网络)。

相关文章
|
2天前
|
Python
【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?
【5月更文挑战第6天】【Python 基础】Python中的实例方法、静态方法和类方法有什么区别?
|
2天前
|
数据处理 Python
Python中每个字段增加多条数据的高效方法
Python中每个字段增加多条数据的高效方法
10 1
|
2天前
|
存储 Python 容器
Python高级编程
Python集合包括可变的set和不可变的frozenset,用于存储无序、不重复的哈希元素。创建集合可使用{}或set(),如`my_set = {1, 2, 3, 4, 5}`。通过add()添加元素,remove()或discard()删除元素,如`my_set.remove(3)`。
|
2天前
|
存储 数据挖掘 Python
Python技术分享:实现选择文件或目录路径的方法
Python技术分享:实现选择文件或目录路径的方法
14 2
|
2天前
|
数据处理 Python
Python中按指定数量分割列表字符串的方法
Python中按指定数量分割列表字符串的方法
8 1
|
3天前
|
测试技术 Python
Python模块化方式编程实践
Python模块化编程提升代码质量,包括:定义专注单一任务的模块;使用`import`导入模块;封装函数和类,明确命名便于重用;避免全局变量降低耦合;使用文档字符串增强可读性;为每个模块写单元测试确保正确性;重用模块作为库;定期维护更新以适应Python新版本。遵循这些实践,可提高代码可读性、重用性和可维护性。
20 2
|
6天前
|
Python
使用Python pandas的sort_values()方法可按一个或多个列对DataFrame排序
使用Python pandas的sort_values()方法可按一个或多个列对DataFrame排序。示例代码展示了如何按'Name'和'Age'列排序 DataFrame。先按'Name'排序,再按'Age'排序。sort_values()的by参数接受列名列表,ascending参数控制排序顺序(默认升序),inplace参数决定是否直接修改原DataFrame。
16 1
|
8天前
|
机器学习/深度学习 数据可视化 前端开发
【Python机器学习专栏】机器学习模型评估的实用方法
【4月更文挑战第30天】本文介绍了机器学习模型评估的关键方法,包括评估指标(如准确率、精确率、召回率、F1分数、MSE、RMSE、MAE及ROC曲线)和交叉验证技术(如K折交叉验证、留一交叉验证、自助法)。混淆矩阵提供了一种可视化分类模型性能的方式,而Python的scikit-learn库则方便实现这些评估。选择适合的指标和验证方法能有效优化模型性能。
|
8天前
|
机器学习/深度学习 算法 Python
【Python机器学习专栏】Python中的特征选择方法
【4月更文挑战第30天】本文介绍了机器学习中特征选择的重要性,包括提高模型性能、减少计算成本和增强可解释性。特征选择方法主要包括过滤法(如相关系数、卡方检验和互信息)、包装法(如递归特征消除和顺序特征选择)和嵌入法(如L1正则化和决策树)。在Python中,可利用`sklearn`库的`feature_selection`模块实现这些方法。通过有效的特征选择,能构建更优的模型并深入理解数据。
|
8天前
|
机器学习/深度学习 数据采集 数据可视化
【Python 机器学习专栏】数据缺失值处理与插补方法
【4月更文挑战第30天】本文探讨了Python中处理数据缺失值的方法。缺失值影响数据分析和模型训练,可能导致模型偏差、准确性降低和干扰分析。检测缺失值可使用Pandas的`isnull()`和`notnull()`,或通过可视化。处理方法包括删除含缺失值的行/列及填充:固定值、均值/中位数、众数或最近邻。Scikit-learn提供了SimpleImputer和IterativeImputer类进行插补。选择方法要考虑数据特点、缺失值比例和模型需求。注意过度插补和验证评估。处理缺失值是提升数据质量和模型准确性关键步骤。