一、性能优化的一般性原则

(1)、依据数据而不是凭空猜测

性能优化的第一原则是当我们怀疑性能有问题的时候,应该通过测试、日志、profillig来分析出哪里有问题,有的放矢,而不是凭感觉、撞运气。一个系统有了性能问题,瓶颈有可能是CPU,有可能是内存,有可能是IO(磁盘IO,网络IO),大方向的定位可以使用top以及stat系列来定位(vmstat,iostat,netstat…),针对单个进程,可以使用pidstat来分析。按照二八定律,绝大多数的时间都耗费在少量的代码片段里面,找出这些代码我们可以使用Profileg工具,熟练使用这些profile工具是性能优化的第一步。

(2)、忌过早优化

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的这句话的意思是:真正的问题是,程序员花了太多时间在错误的地方和错误的时间担心效率;不成熟的优化是编程中所有罪恶(至少是大部分罪恶)的根源。在我们互联网应用开发编程的模式环境下,追求的是快速的迭代与试错,过早的优化往往是无用功。而且,过早的优化很容易拍脑袋,优化的点往往不是真正的性能瓶颈。

(3)、忌过度优化

As performance is part of the specification of a program – a program that is unusably slow is not fit for purpose

由于性能是程序规范的一部分,所以速度慢得不能用的程序是不适合用的。总结下就是性能优化的目标是追求合适的性价比。在不同的阶段,我们对系统的性能会有一定的要求,比如吞吐量要达到多少多少。如果达不到这个指标,就需要去优化。如果能满足预期,那么就无需花费时间精力去优化,比如只有几十个人使用的内部系统,就不用按照十万在线的目标去优化。而且一些优化方法是“有损”的,可能会对代码的可读性、可维护性有副作用。这个时候,就更不能过度优化

(4)、深入理解业务

代码是服务于业务的,也许是服务于最终用户,也许是服务于其他程序员。不了解业务,很难理解系统的流程,很难找出系统设计的不足之处。后面还会提及对业务理解的重要性。

(5)、性能优化是持久战

当核心业务方向明确之后,就应该开始关注性能问题,当项目上线之后,更应该持续的进行性能检测与优化。在上线之后还需要持续的开发,用户的涌入也会带来性能问题。因此需要自动化的检测性能问题,保持稳定的测试环境,持续的发现并解决性能问题,而不是被动地等到用户的投诉。

(6)、选择合适的衡量指标、测试用例、测试环境

正因为性能优化是一个长期的行为,所以需要固定衡量指标、测试用例、测试环境,这样才能客观反映性能的实际情况,也能展现出优化的效果。衡量性能有很多指标,比如系统响应时间、系统吞吐量、系统并发量。不同的系统核心指标是不一样的,首先要明确本系统的核心性能诉求,固定测试用例;其次也要兼顾其他指标,不能顾此失彼。测试环境也很重要,有一次突然发现我们的QPS高了许多,但是程序压根儿没优化,查了半天,才发现是换了一个更牛逼的物理机做测试服务器。

二、性能优化的层次

(1)、需求阶段

不战而屈人之兵,善之善者也。程序员的需求可能来自PM、UI的业务需求(或者说是功能性需求),也可能来自Team Leader的需求。当我们拿到一个需求的时候,首先需要的是思考、讨论需求的合理性,而不是立刻去设计、去编码。需求是为了解决某个问题,问题是本质,需求是解决问题的手段。那么需求是否能否真正的解决问题,程序员也得自己去思考,在之前的文章也提到过,产品经理(特别是知道一点技术的产品经理)的某个需求可能只是某个问题的解决方案,他认为这个方法可以解决他的问题,于是把解决方案当成了需求,而不是真正的问题。需求讨论的前提对业务的深入了解,如果不了解业务,根本没法讨论。即使需求已经实现了,当我们发现有性能问题的时候,首先也可以从需求出发。需求分析对性能优化有什么帮助呢,第一,为了达到同样的目的,解决同样问题,也许可以有性能更优(消耗更小)的办法。这种优化是无损的,即不改变需求本质的同时,又能达到性能优化的效果;第二种情况,有损的优化,即在不明显影响用户的体验,稍微修改需求、放宽条件,就能大大解决性能问题。PM退步一小步,程序前进一大步。需求讨论也有助于设计时更具扩展性,应对未来的需求变化,这里按下不表。

(2)、设计阶段

高手都是花80%时间思考,20%时间实现;新手写起代码来很快,但后面是无穷无尽的修bug。设计的概念很宽泛,包括架构设计、技术选型、接口设计等等。架构设计约束了系统的扩展、技术选型决定了代码实现。编程语言、框架都是工具,不同的系统、业务需要选择适当的工具集。如果设计的时候做的不够好,那么后面就很难优化,甚至需要推到重来。

(3)、实现阶段

实现是把功能翻译成代码的过程,这个层面的优化,主要是针对一个调用流程,一个函数,一段代码的优化。各种profile工具也主要是在这个阶段生效。除了静态的代码的优化,还有编译时优化,运行时优化。后二者要求就很高了,程序员可控性较弱。代码层面,造成性能瓶颈的原因通常是高频调用的函数、或者单次消耗非常高的函数、或者二者的结合。

三、性能优化的一般性方法

(1)、缓存

没有什么性能问题是缓存解决不了的,如果有,那就再加一级缓存。缓存的本质是加速访问,访问的数据要么是其他数据的副本(让数据离用户更近);要么是之前的计算结果 (避免重复计算)。缓存需要用空间换时间,在缓存空间有限的情况下,需要优秀的置换换算来保证缓存有较高的命中率。

(2)、数据的缓存

这是我们最常见的缓存形式,将数据缓存在离使用者更近的地方。比如操作系统中的CPU cache、disk cache。对于一个web应用,前端会有浏览器缓存,有CDN,有反向代理提供的静态内容缓存;后端则有本地缓存、分布式缓存。数据的缓存,很多时候是设计层面的考虑。对于数据缓存,需要考虑的是缓存一致性问题。对于分布式系统中有强一致性要求的场景,可行的解决办法有lease,版本号。

(3)、计算结果的缓存

对于消耗较大的计算,可以将计算结果缓存起来,下次直接使用。我们知道,对递归代码的一个有效优化手段就是缓存中间结果,lookup table,避免了重复计算。python中的method cache就是这种思想。对于可能重复创建、销毁,且创建销毁代价很大的对象,比如进程、线程,也可以缓存,对应的缓存形式如单例、资源池(连接池、线程池)。对于计算结果的缓存,也需要考虑缓存失效的情况,对于pure function,固定的输入有固定的输出,缓存是不会失效的。但如果计算受到中间状态、环境变量的影响,那么缓存的结果就可能失效,比如这篇文章讲到的:http://www.cnblogs.com/xybaby/p/8403461.html#_label_6。

(4)、并发

一个人干不完的活,那就找两个人干。并发既增加了系统的吞吐,又减少了用户的平均等待时间。这里的并发是指广义的并发,粒度包括多机器(集群)、多进程、多线程。对于无状态(状态是指需要维护的上下文环境,用户请求依赖于这些上下文环境)的服务,采用集群就能很好的伸缩,增加系统的吞吐,比如挂载nginx之后的web server。对于有状态的服务,也有两种形式,每个节点提供同样的数据,如mysql的读写分离;每个节点只提供部分数据,如mongodb中的sharding。分布式存储系统中,partition(sharding)和replication(backup)都有助于并发。绝大多数web server,要么使用多进程,要么使用多线程来处理用户的请求,以充分利用多核CPU,再有IO阻塞的地方,也是适合使用多线程的。比较新的协程(Python greenle、goroutine)也是一种并发。

(5)、惰性

将计算推迟到必需的时刻,这样很可能避免了多余的计算,甚至根本不用计算,参见:http://www.cnblogs.com/xybaby/p/6425735.html。

(6)、批量与合并处理

在有IO(网络IO,磁盘IO)的时候,合并操作、批量操作往往能提升吞吐,提高性能。我们最常见的是批量读:每次读取数据的时候多读取一些,以备不时之需。如GFS client会从GFS master多读取一些chunk信息;如分布式系统中,如果集中式节点复杂全局ID生成,俺么应用就可以一次请求一批id。特别是系统中有单点存在的时候,缓存和批量本质上来说减少了与单点的交互,是减轻单点压力的经济有效的方法。在前端开发中,经常会有资源的压缩和合并,也是这种思想。当涉及到网络请求的时候,网络传输的时间可能远大于请求的处理时间,因此合并网络请求就很有必要,比如mognodb的bulk operation,redis 的pipeline。写文件的时候也可以批量写,以减少IO开销,GFS中就是这么干的

(7)、更高效的实现

同一个算法,肯定会有不同的实现,那么就会有不同的性能;有的实现可能是时间换空间,有的实现可能是空间换时间,那么就需要根据自己的实际情况权衡。程序员都喜欢造轮子,用于练手无可厚非,但在项目中,使用成熟的、经过验证的轮子往往比自己造的轮子性能更好。当然不管使用别人的轮子,还是自己的工具,当出现性能的问题的时候,要么优化它,要么替换掉他。比如,我们有一个场景,有大量复杂的嵌套对象的序列化、反序列化,开始的时候是使用python(Cpython)自带的json模块,即使发现有性能问题也没法优化,网上一查,替换成了ujson,性能好了不少。上面这个例子是无损的,但一些更高效的实现也可能是有损的,比如对于python,如果发现性能有问题,那么很可能会考虑C扩展,但也会带来维护性与灵活性的丧失,面临crash的风险。

(8)、充分利用索引

在一个更小的数据范围内进行计算,而不是遍历全部数据(解空间)。最常见的就是索引,通过索引,能够很快定位数据,对数据库的优化绝大多数时候都是对索引的优化。如果有本地缓存,那么使用索引也会大大加快访问速度。不过,索引比较适合读多写少的情况,毕竟索引的构建也是需有消耗的。另外在游戏服务端,使用的分线和AOI(格子算法)也都是缩小解空间的方法。

(9)、性能优化与代码质量

衡量代码质量的标准是可读性、可维护性、可扩展性,但性能优化有可能会违背这些特性,比如为了屏蔽实现细节与使用方式,我们会可能会加入接口层(虚拟层),这样可读性、可维护性、可扩展性会好很多,但是额外增加了一层函数调用,如果这个地方调用频繁,那么也是一笔开销。这种有损代码质量的优化,应该放到最后,不得已而为之,同时写清楚注释与文档。为了追求可扩展性,我们经常会引入一些设计模式,如状态模式、策略模式、模板方法、装饰器模式等,但这些模式不一定是性能友好的。所以,为了性能,我们可能写出一些反模式的、定制化的、不那么优雅的代码,这些代码其实是脆弱的,需求的一点点变动,对代码逻辑可能有至关重要的影响,所以还是回到前面所说,不要过早优化,不要过度优化。

Java虚拟机学习总结(2)——性能优化的一般性原则、层次与通用方法相关推荐

  1. 【读书笔记】实战JAVA虚拟机JVM故障诊断与性能优化 读书笔记

    文章目录 1.概述 1.1 **第一章:初探java虚拟机** 1.2 认识java虚拟机的基本结构 1.3 常用Java虚拟机参数 1.4 垃圾回收器 1.5 垃圾收集器以及内存分配 1.6 性能监 ...

  2. 实战java虚拟机 百度云_《实战JAVA虚拟机 JVM故障诊断与性能优化》pdf百度云下载...

    内容简介· · · · · · 随着越来越多的第三方语言(Groovy.Scala.JRuby等)在Java虚拟机上运行,Java也俨然成为了一个充满活力的生态圈.<实战Java虚拟机--JVM ...

  3. 性能优化指南:性能优化的一般性原则与方法

    作为一个程序员,性能优化是常有的事情,不管是桌面应用还是web应用,不管是前端还是后端,不管是单点应用还是分布式系统.本文从以下几个方面来思考这个问题:性能优化的一般性原则,性能优化的层次,性能优化的 ...

  4. 性能优化的一般性原则和方法​​​​​​​

    看了一篇详细介绍性能优化的博文,感觉很不错,特转载自此.原文地址:性能优化的一般性原则和方法 正文 作为一个程序员,性能优化是常有的事情,不管是桌面应用还是web应用,不管是前端还是后端,不管是单点应 ...

  5. 写给Java程序员的Java虚拟机学习指南

    大家好,我是极客时间<深入拆解Java虚拟机>作者.Oracle Labs高级研究员郑雨迪.有幸借这个专题的机会,能和大家分享为何Java工程师要学Java虚拟机?如何掌握Java虚拟机? ...

  6. java虚拟机_Oracle独家:Java虚拟机学习指南

    大家好,我是极客时间<深入拆解 Java 虚拟机>作者.Oracle Labs 高级研究员郑雨迪.有幸借这个专题的机会,能和大家分享为何 Java 工程师要学 Java 虚拟机?如何掌握 ...

  7. Java虚拟机学习(5):内存调优

    JVM调优主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略.由于GC开始垃圾回收时会挂起应用线程,严重影响了性能,调优的目是为了尽量降低GC所导致的应用线程暂停时间. 减少Full GC次 ...

  8. java虚拟机学习-JVM调优总结-新一代的垃圾回收算法(11)

    java虚拟机学习-深入理解JVM(1) java虚拟机学习-慢慢琢磨JVM(2) java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制 java虚拟机学习-JVM内存管理:深 ...

  9. 深入理解java虚拟机--线程安全与锁优化

    深入理解java虚拟机--线程安全与锁优化 面向过程编程思想和面向对象编程思想 java语言中的线程安全 线程安全的实现方法 锁优化 面向过程编程思想和面向对象编程思想 面向过程编程思想:站在计算机的 ...

最新文章

  1. mysql 查询重写_mysql 学习 - 查询重写规则
  2. MAT之NN:实现BP神经网络的回归拟合,基于近红外光谱的汽油辛烷值含量预测结果对比
  3. genius choice for gopro
  4. 实时音视频助力在线教育风口
  5. 4邻接、8邻接、m邻接
  6. 最新android proguard下载
  7. UESTC 31 饭卡 card
  8. 真不好意思,让你贱笑了
  9. Google 广告考试试题
  10. 计算机公式算加减乘除教程视频,Excel快速计算加减乘除教程 Excel表格公式计算方法...
  11. vue创建一个简易版高德地铁路线图
  12. 高通android q 通过backtrace,使用addr2ine工具,定位crash问题记录
  13. 2021鹅厂产品管培生宣讲会干货总结
  14. 基于Docker搭建DzzOffice与OnlyOffice线上协同办公服务器
  15. 华为PPPoE简单实验
  16. 软件工程毕业设计课题(57)微信小程序毕业设计JAVA投票评选小程序系统设计与实现
  17. 冒泡算法的Java实现
  18. C++行为型模式-实现观察者模式
  19. dll文件下载安装网站
  20. 下行控制信息 - 上行DCI

热门文章

  1. 递归转手动管理栈的非递归
  2. html生日祝福源码大全_2020年全新国庆节手抄报图片大全
  3. 软件的测试文档,软件-测试文档模版.doc
  4. cesium米转换经纬度_Cesium 坐标系转换
  5. html快捷键_Mac进阶:掌握这 5 个冷门快捷键,让Mac更好用
  6. python找不到文件数据_如何用 Python 正确读取资源文件
  7. fetch 不是xhr_fetch与XHR的区别与优势
  8. python 类继承方法_python类的继承、多继承及其常用魔术方法
  9. 201触摸ic应用电路_PCB板和集成电路的区别有哪些
  10. pytorch torch.squeeze