这是性能优化的第一原则,当咱们怀疑性能有问题的时候,应该经过测试、日志、profillig来分析出哪里有问题,有的放矢,而不是凭感受、撞运气。一个系统有了性能问题,瓶颈有多是CPU,有多是内存,有多是IO(磁盘IO,网络IO),大方向的定位可使用top以及stat系列来定位(vmstat,iostat,netstat...),针对单个进程,可使用pidstat来分析。html
在本文中,主要讨论的是CPU相关的性能问题。按照80/20定律,绝大多数的时间都耗费在少许的代码片断里面,找出这些代码惟一可靠的办法就是profile,我所知的编程语言,都有相关的profile工具,熟练使用这些profile工具是性能优化的第一步。前端
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
我并不十分清楚Donald Knuth说出这句名言的上下文环境,但我本身是十分认同这个观念的。在个人工做环境(以及典型的互联网应用开发)与编程模式下,追求的是快速的迭代与试错,过早的优化每每是无用功。并且,过早的优化很容易拍脑壳,优化的点每每不是真正的性能瓶颈。python
As performance is part of the specification of a program – a program that is unusably slow is not fit for purpose
性能优化的目标是追求合适的性价比。mysql
在不一样的阶段,咱们对系统的性能会有必定的要求,好比吞吐量要达到多少多少。若是达不到这个指标,就须要去优化。若是能知足预期,那么就无需花费时间精力去优化,好比只有几十我的使用的内部系统,就不用按照十万在线的目标去优化。ios
并且,后面也会提到,一些优化方法是“有损”的,可能会对代码的可读性、可维护性有反作用。这个时候,就更不能过分优化。nginx
代码是服务于业务的,也许是服务于最终用户,也许是服务于其余程序员。不了解业务,很难理解系统的流程,很难找出系统设计的不足之处。后面还会说起对业务理解的重要性。程序员
当核心业务方向明确以后,就应该开始关注性能问题,当项目上线以后,更应该持续的进行性能检测与优化。web
如今的互联网产品,再也不是一锤子买卖,在上线以后还须要持续的开发,用户的涌入也会带来性能问题。所以须要自动化的检测性能问题,保持稳定的测试环境,持续的发现并解决性能问题,而不是被动地等到用户的投诉。面试
正由于性能优化是一个长期的行为,因此须要固定衡量指标、测试用例、测试环境,这样才能客观反映性能的实际状况,也能展示出优化的效果。redis
衡量性能有不少指标,好比系统响应时间、系统吞吐量、系统并发量。不一样的系统核心指标是不同的,首先要明确本系统的核心性能诉求,固定测试用例;其次也要兼顾其余指标,不能顾此失彼。
测试环境也很重要,有一次忽然发现咱们的QPS高了许多,可是程序压根儿没优化,查了半天,才发现是换了一个更牛逼的物理机作测试服务器。
(https://www.cnblogs.com/xybab...
按照个人理解能够分为需求阶段,设计阶段,实现阶段;越上层的阶段优化效果越明显,同时也更须要对业务、需求的深刻理解。
不战而屈人之兵,善之善者也
程序员的需求可能来自PM、UI的业务需求(或者说是功能性需求),也可能来自Team Leader的需求。当咱们拿到一个需求的时候,首先须要的是思考、讨论需求的合理性,而不是马上去设计、去编码。
需求是为了解决某个问题,问题是本质,需求是解决问题的手段。那么需求是否可否真正的解决问题,程序员也得本身去思考,在以前的文章也提到过,产品经理(特别是知道一点技术的产品经理)的某个需求可能只是某个问题的解决方案,他认为这个方法能够解决他的问题,因而把解决方案当成了需求,而不是真正的问题。
需求讨论的前提对业务的深刻了解,若是不了解业务,根本无法讨论。即便需求已经实现了,当咱们发现有性能问题的时候,首先也能够从需求出发。
需求分析对性能优化有什么帮助呢,第一,为了达到一样的目的,解决一样问题,也许能够有性能更优(消耗更小)的办法。这种优化是无损的,即不改变需求本质的同时,又能达到性能优化的效果;第二种状况,有损的优化,即在不明显影响用户的体验,稍微修改需求、放宽条件,就能大大解决性能问题。PM退步一小步,程序前进一大步。
需求讨论也有助于设计时更具扩展性,应对将来的需求变化,这里按下不表。
高手都是花80%时间思考,20%时间实现;新手写起代码来很快,但后面是无穷无尽的修bug
设计的概念很宽泛,包括架构设计、技术选型、接口设计等等。架构设计约束了系统的扩展、技术选型决定了代码实现。编程语言、框架都是工具,不一样的系统、业务须要选择适当的工具集。若是设计的时候作的不够好,那么后面就很难优化,甚至须要推到重来。
实现是把功能翻译成代码的过程,这个层面的优化,主要是针对一个调用流程,一个函数,一段代码的优化。各类profile工具也主要是在这个阶段生效。除了静态的代码的优化,还有编译时优化,运行时优化。后两者要求就很高了,程序员可控性较弱。
代码层面,形成性能瓶颈的缘由一般是高频调用的函数、或者单次消耗很是高的函数、或者两者的结合。
下面介绍针对设计阶段与实现阶段的优化手段。
(https://www.cnblogs.com/xybab...
没有什么性能问题是缓存解决不了的,若是有,那就再加一级缓存
a cache /kæʃ/ KASH,[1] is a hardware or software component that stores data so future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation, or the duplicate of data stored elsewhere.
缓存的本质是加速访问,访问的数据要么是其余数据的副本 -- 让数据离用户更近;要么是以前的计算结果 -- 避免重复计算.
缓存须要用空间换时间,在缓存空间有限的状况下,须要优秀的置换换算来保证缓存有较高的命中率。
这是咱们最多见的缓存形式,将数据缓存在离使用者更近的地方。好比操做系统中的CPU cache、disk cache。对于一个web应用,前端会有浏览器缓存,有CDN,有反向代理提供的静态内容缓存;后端则有本地缓存、分布式缓存。
数据的缓存,不少时候是设计层面的考虑。
对于数据缓存,须要考虑的是缓存一致性问题。对于分布式系统中有强一致性要求的场景,可行的解决办法有lease,版本号。
对于消耗较大的计算,能够将计算结果缓存起来,下次直接使用。
咱们知道,对递归代码的一个有效优化手段就是缓存中间结果,lookup table,避免了重复计算。python中的method cache就是这种思想.
对于可能重复建立、销毁,且建立销毁代价很大的对象,好比进程、线程,也能够缓存,对应的缓存形式如单例、资源池(链接池、线程池)。
对于计算结果的缓存,也须要考虑缓存失效的状况,对于pure function,固定的输入有固定的输出,缓存是不会失效的。但若是计算受到中间状态、环境变量的影响,那么缓存的结果就可能失效,好比我在前面提到的python method cache
一我的干不完的活,那就找两我的干。并发既增长了系统的吞吐,又减小了用户的平均等待时间。
这里的并发是指广义的并发,粒度包括多机器(集群)、多进程、多线程。
对于无状态(状态是指须要维护的上下文环境,用户请求依赖于这些上下文环境)的服务,采用集群就能很好的伸缩,增长系统的吞吐,好比挂载nginx以后的web server
对于有状态的服务,也有两种形式,每一个节点提供一样的数据,如mysql的读写分离;每一个节点只提供部分数据,如mongodb中的sharding
分布式存储系统中,partition(sharding)和replication(backup)都有助于并发。
绝大多数web server,要么使用多进程,要么使用多线程来处理用户的请求,以充分利用多核CPU,再有IO阻塞的地方,也是适合使用多线程的。比较新的协程(Python greenle、goroutine)也是一种并发。
将计算推迟到必需的时刻,这样极可能避免了多余的计算,甚至根本不用计算,这个在以前的《lazy ideas in programming》一文中举了许多例子。
CopyOnWrite这个思想真牛逼!
在有IO(网络IO,磁盘IO)的时候,合并操做、批量操做每每能提高吞吐,提升性能。
咱们最多见的是批量读:每次读取数据的时候多读取一些,以备不时之需。如GFS client会从GFS master多读取一些chunk信息;如分布式系统中,若是集中式节点复杂全局ID生成,俺么应用就能够一次请求一批id。
特别是系统中有单点存在的时候,缓存和批量本质上来讲减小了与单点的交互,是减轻单点压力的经济有效的方法
在前端开发中,常常会有资源的压缩和合并,也是这种思想。
当涉及到网络请求的时候,网络传输的时间可能远大于请求的处理时间,所以合并网络请求就颇有必要,好比mognodb的bulk operation,redis 的pipeline。写文件的时候也能够批量写,以减小IO开销,GFS中就是这么干的
同一个算法,确定会有不一样的实现,那么就会有不一样的性能;有的实现多是时间换空间,有的实现多是空间换时间,那么就须要根据本身的实际状况权衡。
程序员都喜欢早轮子,用于练手无可厚非,但在项目中,使用成熟的、通过验证的轮子每每比本身造的轮子性能更好。固然无论使用别人的轮子,仍是本身的工具,当出现性能的问题的时候,要么优化它,要么替换掉他。
好比,咱们有一个场景,有大量复杂的嵌套对象的序列化、反序列化,开始的时候是使用python(Cpython)自带的json模块,即便发现有性能问题也无法优化,网上一查,替换成了ujson,性能好了很多。
上面这个例子是无损的,但一些更高效的实现也多是有损的,好比对于python,若是发现性能有问题,那么极可能会考虑C扩展,但也会带来维护性与灵活性的丧失,面临crash的风险。
缩小解空间的意思是说,在一个更小的数据范围内进行计算,而不是遍历所有数据。最多见的就是索引,经过索引,可以很快定位数据,对数据库的优化绝大多数时候都是对索引的优化。
若是有本地缓存,那么使用索引也会大大加快访问速度。不过,索引比较适合读多写少的状况,毕竟索引的构建也是需有消耗的。
另外在游戏服务端,使用的分线和AOI(格子算法)也都是缩小解空间的方法。
(https://www.cnblogs.com/xybab...
不少时候,好的代码也是高效的代码,各类语言都会有一本相似的书《effective xx》。好比对于python,pythonic的代码一般效率都不错,如使用迭代器而不是列表(python2.7 dict的iteritems(), 而不是items())。
衡量代码质量的标准是可读性、可维护性、可扩展性,但性能优化有可能会违背这些特性,好比为了屏蔽实现细节与使用方式,咱们会可能会加入接口层(虚拟层),这样可读性、可维护性、可扩展性会好不少,可是额外增长了一层函数调用,若是这个地方调用频繁,那么也是一笔开销;又如前面提到的C扩展,也是会下降可维护性、
这种有损代码质量的优化,应该放到最后,不得已而为之,同时写清楚注释与文档。
为了追求可扩展性,咱们常常会引入一些设计模式,如状态模式、策略模式、模板方法、装饰器模式等,但这些模式不必定是性能友好的。因此,为了性能,咱们可能写出一些反模式的、定制化的、不那么优雅的代码,这些代码实际上是脆弱的,需求的一点点变更,对代码逻辑可能有相当重要的影响,因此仍是回到前面所说,不要过早优化,不要过分优化。
(https://www.cnblogs.com/xybab...
来张脑图总结一下
TCP的三次握手,四次挥手加IP、SOCKET全景图,面试有备无患!https://www.bilibili.com/video/BV1HQ4y1M7oe/
资深架构师马士兵读解多线程、线程池原理,给Java程序员职业把脉,让你薪资翻一翻!https://www.bilibili.com/video/BV1BE411A78Z/
阿里P9架构师120分钟带你掌握线程池,不在为线程而烦恼
https://www.bilibili.com/video/BV1GE411N7sc/