全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2020-01-23_「转」如何使Python程序快如闪电,提速30%?

您的位置:首页 >> 新闻 >> 行业资讯

如何使Python程序快如闪电,提速30%? 策划 | 刘燕 作者 | Martin Heinz 译者 | 平川 编辑 | Linda AI 前线导读:讨厌 Python 的人总是说,他们不想使用它的原因之一是它很慢。不管使用什么编程语言,程序是快还是慢都在很大程度上取决于编写程序的开发人员,以及他们编写最优化快速程序的技能和能力。在本文中,让我们来证明一下某些人的“误解”,看看如何提高 Python 程序的性能,使它们变得非常快! 更多优质内容请关注微信公众号“AI 前线”(ID:ai-front) 本文最初发布于 martinheinz.dev 博客,经原作者授权由 InfoQ 中文站翻译并分享。 计时和性能分析 在我们开始优化任何东西之前,我们首先需要找出到底是代码的哪些部分减慢了整个程序。有时候,程序的瓶颈可能是显而易见的,但如果你不知道它在哪里,那么以下选项可以帮你找出来。 这是我将用于演示的程序,它计算 e 的 X 次方(摘自 Python 文档): # slow_program.py from decimal import * def exp(x): getcontext().prec += 2 i, lasts, s, fact, num = 0, 0, 1, 1, 1 while s != lasts: lasts = s i += 1 fact *= i num *= x s += num / fact getcontext().prec -= 2 return +s exp(Decimal(150)) exp(Decimal(400)) exp(Decimal(3000)) 最懒的“性能分析” 首先是最简单同时又非常懒惰的解决方案——Unix time 命令: ~ $ time python3.8 slow_program.py real 0m11,058s user 0m11,050s sys 0m0,008s 如果你只是想计算整个程序的运行时间,这就行了,但这通常不能满足需求…… 最详细的性能分析 另一个极端是 cProfile,它提供的信息又太多了: ~ $ python3.8 -m cProfile -s time slow_program.py 1297 function calls (1272 primitive calls) in 11.081 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 3 11.079 3.693 11.079 3.693 slow_program.py:4(exp) 1 0.000 0.000 0.002 0.002 {built-in method _imp.create_dynamic} 4/1 0.000 0.000 11.081 11.081 {built-in method builtins.exec} 6 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x9d12c0} 6 0.000 0.000 0.000 0.000 abc.py:132(__new__) 23 0.000 0.000 0.000 0.000 _weakrefset.py:36(__init__) 245 0.000 0.000 0.000 0.000 {built-in method builtins.getattr} 2 0.000 0.000 0.000 0.000 {built-in method marshal.loads} 10 0.000 0.000 0.000 0.000 frozen importlib._bootstrap_external:1233(find_spec) 8/4 0.000 0.000 0.000 0.000 abc.py:196(__subclasscheck__) 15 0.000 0.000 0.000 0.000 {built-in method posix.stat} 6 0.000 0.000 0.000 0.000 {built-in method builtins.__build_class__} 1 0.000 0.000 0.000 0.000 __init__.py:357(namedtuple) 48 0.000 0.000 0.000 0.000 frozen importlib._bootstrap_external:57(_path_join) 48 0.000 0.000 0.000 0.000 frozen importlib._bootstrap_external:59(listcomp) 1 0.000 0.000 11.081 11.081 slow_program.py:1(module) 在这里,我们使用 cProfile 模块和 time 参数运行测试脚本,这样就可以根据内部时间(cumtime)对代码行进行排序。这给了我们很多信息,上面的内容大约是实际输出的 10%。从这里,我们可以看到 exp 函数是罪魁祸首(惊喜!),现在我们可以得到更具体的时间和性能分析… 对具体的函数计时 现在我们知道了应该将注意力放在哪里,我们可能希望对慢速函数进行计时,而不需要测量代码的其余部分。我们可以使用简单的装饰器: def timeit_wrapper(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() # Alternatively, you can use time.process_time() func_return_val = func(*args, **kwargs) end = time.perf_counter() print('{0:10}.{1:8} : {2:8}'.format(func.__module__, func.__name__, end - start)) return func_return_val return wrapper 接下来,可以把这个装饰器应用到函数上,像下面这样: @timeit_wrapper def exp(x): ... print('{0:10} {1:8} {2:^8}'.format('module', 'function', 'time')) exp(Decimal(150)) exp(Decimal(400)) exp(Decimal(3000)) 输出如下: ~ $ python3.8 slow_program.py module function time __main__ .exp : 0.003267502994276583 __main__ .exp : 0.038535295985639095 __main__ .exp : 11.728486061969306 需要考虑的一件事是我们实际上(想)测量的是哪种时间。时间包提供了 time.perf_counter 和 time.process_time。它们的不同之处在于 perf_counter 返回绝对值,其中包括 Python 程序进程不运行时的时间,因此可能会受到机器负载的影响。另一方面,process_time 只返回用户时间(不包括系统时间),只是进程的时间。 使之变快 有趣的部分来了。我们将让你的 Python 程序运行得更快一些。我(基本上)不会向你展示一些能够神奇地解决性能问题的骇客技术、技巧和代码片段。这里介绍的更多的是一般的想法和策略,当你使用它们时,可以对性能产生巨大的影响,在某些情况下可以提高 30% 的速度。 使用内置数据类型 这一点很明显。内置数据类型非常快,特别是与树或链表等自定义类型相比。这主要是因为内置类型是用 C 实现的,在用 Python 编码时,我们无法在速度上与之匹配。 使用 lru_cache 缓存数据 我已经在之前的 博文 中介绍过这个,但是我认为值得通过一个简单的例子再说明一下: import functools import time # 最多缓存 12 个不同的结果 @functools.lru_cache(maxsize=12) def slow_func(x): time.sleep(2) # 模拟长时间计算 return x slow_func(1) # ... 等待 2 秒才能获得结果 slow_func(1) # 结果已缓存,会立即返回 slow_func(3) # ... 等待 2 秒才能获得结果 上面的函数使用 time.sleep 模拟大量计算。第一次使用参数 1 调用时,它将等待 2 秒,然后才返回结果。当再次调用时,结果已经被缓存,因此,它会跳过函数体并立即返回结果。要了解更多真实的例子,请点击 这里 查看以前的博文。 使用局部变量 这与在每个作用域内查找变量的速度有关。我会写每个作用域,因为它不只关乎使用局部变量还是全局变量。查找速度也确实存在差异,函数中的局部变量最快,类级属性(例如 self.name)次之,而全局(例如导入的函数 time.time)变量最慢。 你可以像下面这样,使用不必要的赋值来提升性能: # 示例 #1 class FastClass: def do_stuff(self): temp = self.value # 这可以加速循环中的查找 for i in range(10000): ... # 在这里使用`temp`做些操作 # 示例 #2 import random def fast_function(): r = random.random for i in range(10000): print(r()) # 在这里调用`r()`,比全局的 random.random() 要快 使用函数 这看起来可能不符合直觉,因为调用函数会将更多的东西放到堆栈中,从函数返回时会产生开销,但这与前面一点有关。如果你只是将整个代码放入一个文件中,而不将其放入函数中,那么由于全局变量的关系,速度会慢很多。因此,你只是将整个代码封装在 main 函数中并调用一次,就可以加快你的代码,像这样: def main(): ... # 之前所有的全局代码 main() 不要访问属性 另一个可能降低程序速度的是点操作符(.),它可以用于访问对象属性。这个操作符使用 _getattribute__ 触发字典查找,这会在代码中产生额外的开销。那么,我们如何才能避免(限制)使用它呢? # 慢: import re def slow_func(): for i in range(10000): re.findall(regex, line) # 慢! # 快: from re import findall def fast_func(): for i in range(10000): findall(regex, line) # 较快! 提防字符串 在循环中运行诸如模数(%s)或.format() 之类的方法时,对字符串的操作可能会变得非常慢。我们还有什么更好的选择吗?根据 Raymond Hettinger 最近的 推文,我们唯一应该使用的是 f-string,它是最易读、最简洁、最快速的方法。因此,根据那条推文,你可以使用以下方法——从最快的到最慢的: f'{s} {t}' # 快! s + ' ' + t ' '.join((s, t)) '%s %s' % (s, t) '{} {}'.format(s, t) Template('$s $t').substitute(s=s, t=t) # 慢! 生成器本身并没有更快,因为它们允许延迟计算,这节省的是内存而不是时间。但是,节省的内存可能会使得程序在实际运行时更快。为什么?如果你有一个大型数据集,并且没有使用生成器(迭代器),那么数据可能会溢出 CPU L1 缓存,这将显著降低在内存中查找值的速度。 说到性能,很重要的一点是 CPU 可以将它正在处理的所有数据保存在缓存中。你可以看下 Raymond Hettingers 的演讲,他提到了这些问题。 小 结 优化的第一原则是不做优化。但是,如果你真的需要,我希望这些小技巧能帮到你。不过,在优化代码时要注意,因为它可能会使代码难于阅读、难于维护,甚至超过优化带来的好处。 原文链接: https://martinheinz.dev/blog/13 你也「在看」吗??? 阅读原文

上一篇:2023-09-13_与 x86、ARM 三分天下,全球“开花”的 RISC-V 如何成为中国最受欢迎芯片架构? 下一篇:2021-08-12_为什么神经网络不适合理解自然语言 ?

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价