首页 / 资讯中心 / 文章详情

让代码飞起来:高效优化的十大秘诀

让代码飞起来:高效优化的十大秘诀 ★ FEATURED ARTICLE
这篇文章要跟大家说说, 到底要怎么做, 才能够提升咱们那个程序的运行效率呢, 就是让它们跑得飞快起来那些很讨厌的人, 总是会说一些话, 他们不想使用某个东西, 有一个很重要的原因, 就是那个东西很慢。而实际上的事实是, 不管我们使用什么样的编程语言, 某一个特定的程序的运行速度在很大的程度上, 会取决于编写这个程序的开发人员, 以及他们优化程序的技能, 还有他们加快程序运行速度的能力。那么, 我们现在就来证明说这些话的人, 他们是错误的。这篇重要的文章, 将详细介绍如何提升某个程序的效率, 让这些程序运行得飞快无比在进行优化操作之前, 咱们得先搞清楚代码里的哪块内容真正拖慢了整个程序的运行速度。有时候, 程序性能出现瓶颈的地方是特别明显的那种情况, 但当咱们完全不知道瓶颈具体位置在哪儿时, 这儿有一些方法能够帮咱们找到性能瓶颈所在的区域, 这里需要做个备注, 下面用来做演示的那些程序, 它的功能是计算数学常数 e 的 X 次方那个值, 这个示例是从文档里面摘取出来的。由于原始输入内容仅为无意义的乱码字符堆砌, 既没有构成符合人类语言逻辑的语法结构, 也不包含任何需要通过现在人们能直接看懂的普通话书面语进行转换的有效语义信息, 因此不存在“改写”的可能。最懒惰的「性能分析」, 首先是, 这个方法既简单, 可是说实话呢, 也很懒散——具体做法是使用 Unix 当中的 time 这个命令:~$..,,,008s如果你只是打算对整一个程序进行时间测量, 那么使用这个命令完全能够达成你的目的, 不过这通常来讲是不够的, 至于说到最细致的那种性能分析的话, 它就走向了另一个极端的方面, 从而提供了属于「太多」范畴内的相关信息:~$.8--.()in11.::()311.0793.69311.0793..py:4(exp)10.0000.0000.0020.002{built-.}4/10.0000.00011.08111.081{built-.exec}60.0000.0000.0000.000{built-}60.0000.0000.0000..py:132()230.0000.0000.0000..py:36()2450.0000.0000.0000.000{built-.}20.0000.0000.0000.000{built-.loads}100.0000.0000.0000.000:1233()8/40.0000.0000.0000..py:196()150.0000.0000.0000.000{built-.stat}60.0000.0000.0000.000{built-.}10.0000.0000.0000..py:357()480.0000.0000.0000.000:57()480.0000.0000.0000.000:59()10.0000.00011.08111..py:1()...在这里, 我们把模块以及时间参数拿来一起用, 去运行那个测试用的脚本。这么做是为了让输出来的那些行子, 照着它们里面的时间顺序去排好。这样我们就能拿到特别多的信息, 至于你刚才看到的那些, 不过是实际出来的全部内容的一小部分, 大概只占了百分之十的份额。从输出的结果来看, 我们清楚地看到 exp 函数是最大的问题来源这难道不是让人感到非常意外和惊喜吗, 如此一来, 现在我们完全可以集中更多的精力去开展计时工作以及进行性能方面的分析……关于专门的计时方法既然我们已经明确了应当关注的重点所在, 那么或许大家此刻仅仅希望给那些运行速度比较缓慢的函数加上计时的功能, 而不需要再去关注代码里其他部分的状况。为了能够达到这个目的, 我们可以采用一个简单的设计模式, 也就是通常所说的装饰器, 具体的做法如下所示:在函数开始执行之前, 我们记录当前的系统时间, 也就是把此时的时刻保存在一个变量里头去, 紧接着让原函数带着它接收到的所有位置参数和关键字参数正式跑起来完成它的任务, 等函数执行完毕后, 我们再记下一遍当下的时间点, 随后把这两个时间戳拿出来通过一种格式化字符串的规则给打印输出出来以表示这段代码的执行周期信息内容详情。然后, 按照下面所提到的具体方式去对待测的这个函数进行装饰器的应用操作。(x):...print({0:最终获得到的结果呈现为下面的这种状态形式。这个表示从exp:0开始到exp:0, 最后再到exp:11的一个范围或者序列。现在, 咱们得琢磨一下, 究竟要量度是哪一种时间规格。time 这个工具库里头, 给出了 time. 以及 time. 这两样东西。它们俩的差别体现在哪里? 那个会返回绝对的数值, 把程序根本没有在跑的时间也给算进去了。所以说, 它很有可能会被机器的负载压力给带偏了。而剩下那个, 只负责吐出用户时间, 把系统时间给撇开不要, 简单说, 就是单单只有进程正在运行所花费的时间。现在来到了真正有意思的阶段, 就是让程序跑得更快。我打算不讲那些奇技淫巧或者代码片段去神奇地解决程序性能问题, 而是更多地聊聊通用的想法和策略。如果用了这些策略, 可能会对程序的性能产生很大的影响, 有时候甚至能提高百分之三十的速度。使用内置的数据类型这点是很明显的。内置的数据类型非常快, 这尤其相比于树或链表等自定义类型而言的情况来说是非常明显的。造成这个现象主要是因为内置数据类型使用 C 语言实现这一点。使用其他语言或者是其他机制实现的代码在运行速度上和它们是没法比的。关于使用缓存技巧或者记忆技巧来实现优化的事情, 我在之前的博客中介绍过这样的技巧了。但是现在我认为它仍然值得用一个比较简单的例子再次进行说明这一观点:ttime#.(12)(x):time.sleep(2)#(1)#...nc(1)#-!(3)#...上述的函数运用了time.sleep模块来模拟进行高强度计算的过程。当我们在初始阶段以参数 1 调用该函数的时候, 它经过了长达 2 秒的等待之后才把最终的计算结果给反馈出来。然而到了下一次调用的时刻, 因为之前的运算结果已经存在了缓存之中, 所以整个操作流程直接越过了函数体内部的执行环节, 转而立刻将储存好的结果给返回。这种做法主要是利用了局部变量的特性, 而这其中的原理和大家对每一个不同作用域里面变量进行的查找速度的认识是紧密相关的。我之所以提到「每个作用域」, 原因并不仅仅是因为涉及到局部变量或者是全局变量这么简单, 事实情况是, 函数里面的局部变量、类级别的属性, 还有全局导入的函数这三者,它们的查找速度其实是存在一些区别的, 其中函数内部的局部变量速度最快, 类级别的属性比如 self.name 这种情况稍微慢一点, 而全局导入的函数像 time.time 这种是最慢的。用户完全能够通过使用这种方式这种看起来毫无必要的代码组织方法, 来让工作效率得到提升。首先我们将代码中的 self.value的值赋给变量 temp, 这里需要执行一万次相关的操作, 然后定义了一个函数 r, 在这个函数的逻辑中同样涉及一万次的处理过程, 最后调用这个叫做 r 的函数进行输出展示。使用函数这或许让人觉得不太合乎常理, 毕竟函数调用会迫使大量数据入栈, 从而导致在程序执行完毕并返回时增加额外的负担, 然而这种现象实际上与先前所采用的策略存在紧密的联系。倘若你仅仅将所有的代码片段随意堆砌在一个文件之中, 而不借助函数进行结构化处理, 那么整个程序就有可能因为需要管理数目庞大的全局变量而大幅降低运行速度。为了规避这一不利情况, 我们可以采取相应的改进手段, 即将全部代码整合到一个特定的主功能模块之内, 随后通过触发该模块来启动整个流程, 以此途径达成提升性能的目标, 具体的实施方法可参见下述示范内容:():...#()访问对象属性时使用的那个 dot 运算符, 是另一个可能让程序变慢的东西。它会引发字典查找操作, 从而给程序带来不必要的开销。那么, 我们应该怎么避免这个情况? 或者该怎么限制它的使用呢?#Slow:():(10000):re.(regex,line)#Slow!#Fast:():(10000):(regex,line)#!大家需要注意了, 如果在循环里面去使用取模运算符这个符号是百分号后面跟着字符s或者点括号这种形式来处理字符串的话, 那么字符串的操作速度就会变得慢很多。到底有没有更好的方法可以选择呢?根据最近发布在推特上的内容, 告诉大家一个事实, 我们只要使用以f开头的那种格式就行了, 这种方式不仅容易看懂, 代码写起来更加简洁紧凑, 运行起来的速度还特别快按照这样的思路, 下面列出来的方式是从速度最快到最慢的一种排列顺序, 这些都是你可以使用的方法:用f{s}{t}这种写法速度非常快。还有, st.join((s,t)), %s%s%(s,t), {}{}.(s,t)($s$t).(ss,tt)这种写法速度比较慢。生成器, 从本质上来说, 并不会让速度变得更快, 这是因为它们的核心目标就是执行惰性计算这个动作, 也就是为了节省内存空间而设计出的方案, 并不是出于去节省时间这个目的的考量。然而, 因为节省了那么多内存, 这确实可能会让整个程序的运行速度变快一些。这里面的原因是怎么样的呢?如果你现在手头有一个特别大的数据集, 并且你没有采用生成这种迭代器的处理方式的话, 那么这些大量的数据很有可能会导致计算机中央处理器的 L1 缓存空间发生溢出情况, 一旦出现这种情况的话, 就会使得进行数据存储和读取操作的速度出现明显的下降现象, 从而造成访存速度显著变慢的结果。当提到效率这个方面的问题时, 有一条非常重要的原则必须要记住, 就是 CPU 会把正在处理的数据保存到尽量靠近它自己的位置去, 换句话说, 这些数据是被保存在缓存里面的。读者不妨去看一看相关的演讲资料, 在里面会涉及到很多关于这些技术细节的分析。经过总结可以发现, 优化的第一核心原则其实就是「不要去动手做」多余的操作。但是, 如果你确实没办法回避, 必须执行某些动作的话, 我真心希望上述那些微小的技巧真的能够对你有所裨益。可是, 我们在对代码进行优化的时候务必要格外小心。这是因为这种操作极有可能导致程序的可读性下降, 并且也会使得后续的维护工作变得更加困难。由此产生的这些负面因素有时候甚至可能比完成优化后所能够获得的那些益处还要多。
阅读完成 · 觉得有帮助?
咨询建站