点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

作者:xybaby

cnblogs.com/xybaby/p/9055734.html

作为一个程序员,性能优化是常有的事情,不管是桌面应用还是web应用,不管是前端还是后端,不管是单点应用还是分布式系统。本文从以下几个方面来思考这个问题:性能优化的一般性原则,性能优化的层次,性能优化的通用方法。本文不限于任何语言、框架,不过可能会用Python语言来举例。

不过囿于个人经验,可能更多的是从Linux服务端的角度来思考这些问题。


一般性原则

依据数据而不是凭空猜测

这是性能优化的第一原则,当我们怀疑性能有问题的时候,应该通过测试、日志、profillig来分析出哪里有问题,有的放矢,而不是凭感觉、撞运气。一个系统有了性能问题,瓶颈有可能是CPU,有可能是内存,有可能是IO(磁盘IO,网络IO),大方向的定位可以使用top以及stat系列来定位(vmstat,iostat,netstat…),针对单个进程,可以使用pidstat来分析。

在本文中,主要讨论的是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说出这句名言的上下文环境,但我自己是十分认同这个观念的。在我的工作环境(以及典型的互联网应用开发)与编程模式下,追求的是快速的迭代与试错,过早的优化往往是无用功。而且,过早的优化很容易拍脑袋,优化的点往往不是真正的性能瓶颈。

忌过度优化

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

性能优化的目标是追求合适的性价比。

在不同的阶段,我们对系统的性能会有一定的要求,比如吞吐量要达到多少多少。如果达不到这个指标,就需要去优化。如果能满足预期,那么就无需花费时间精力去优化,比如只有几十个人使用的内部系统,就不用按照十万在线的目标去优化。

而且,后面也会提到,一些优化方法是“有损”的,可能会对代码的可读性、可维护性有副作用。这个时候,就更不能过度优化。

深入理解业务

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

性能优化是持久战

当核心业务方向明确之后,就应该开始关注性能问题,当项目上线之后,更应该持续的进行性能检测与优化。

现在的互联网产品,不再是一锤子买卖,在上线之后还需要持续的开发,用户的涌入也会带来性能问题。因此需要自动化的检测性能问题,保持稳定的测试环境,持续的发现并解决性能问题,而不是被动地等到用户的投诉。

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

正因为性能优化是一个长期的行为,所以需要固定衡量指标、测试用例、测试环境,这样才能客观反映性能的实际情况,也能展现出优化的效果。

衡量性能有很多指标,比如系统响应时间、系统吞吐量、系统并发量。不同的系统核心指标是不一样的,首先要明确本系统的核心性能诉求,固定测试用例;其次也要兼顾其他指标,不能顾此失彼。

测试环境也很重要,有一次突然发现我们的QPS高了许多,但是程序压根儿没优化,查了半天,才发现是换了一个更牛逼的物理机做测试服务器。


性能优化的层次

按照我的理解可以分为需求阶段,设计阶段,实现阶段;越上层的阶段优化效果越明显,同时也更需要对业务、需求的深入理解。

需求阶段

不战而屈人之兵,善之善者也

程序员的需求可能来自PM、UI的业务需求(或者说是功能性需求),也可能来自Team Leader的需求。当我们拿到一个需求的时候,首先需要的是思考、讨论需求的合理性,而不是立刻去设计、去编码。

需求是为了解决某个问题,问题是本质,需求是解决问题的手段。那么需求是否能否真正的解决问题,程序员也得自己去思考,在之前的文章也提到过,产品经理(特别是知道一点技术的产品经理)的某个需求可能只是某个问题的解决方案,他认为这个方法可以解决他的问题,于是把解决方案当成了需求,而不是真正的问题。

需求讨论的前提对业务的深入了解,如果不了解业务,根本没法讨论。即使需求已经实现了,当我们发现有性能问题的时候,首先也可以从需求出发。

需求分析对性能优化有什么帮助呢,第一,为了达到同样的目的,解决同样问题,也许可以有性能更优(消耗更小)的办法。这种优化是无损的,即不改变需求本质的同时,又能达到性能优化的效果;第二种情况,有损的优化,即在不明显影响用户的体验,稍微修改需求、放宽条件,就能大大解决性能问题。PM退步一小步,程序前进一大步。

需求讨论也有助于设计时更具扩展性,应对未来的需求变化,这里按下不表。

设计阶段

高手都是花80%时间思考,20%时间实现;新手写起代码来很快,但后面是无穷无尽的修bug

设计的概念很宽泛,包括架构设计、技术选型、接口设计等等。架构设计约束了系统的扩展、技术选型决定了代码实现。编程语言、框架都是工具,不同的系统、业务需要选择适当的工具集。如果设计的时候做的不够好,那么后面就很难优化,甚至需要推到重来。

实现阶段

实现是把功能翻译成代码的过程,这个层面的优化,主要是针对一个调用流程,一个函数,一段代码的优化。各种profile工具也主要是在这个阶段生效。除了静态的代码的优化,还有编译时优化,运行时优化。后二者要求就很高了,程序员可控性较弱。

代码层面,造成性能瓶颈的原因通常是高频调用的函数、或者单次消耗非常高的函数、或者二者的结合。

下面介绍针对设计阶段与实现阶段的优化手段。


一般性方法

缓存

没有什么性能问题是缓存解决不了的,如果有,那就再加一级缓存

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,固定的输入有固定的输出,缓存是不会失效的。但如果计算受到中间状态、环境变量的影响,那么缓存的结果就可能失效,比如这篇文章讲到的:

http://www.cnblogs.com/xybaby/p/8403461.html#_label_6

并发

一个人干不完的活,那就找两个人干。并发既增加了系统的吞吐,又减少了用户的平均等待时间。

这里的并发是指广义的并发,粒度包括多机器(集群)、多进程、多线程。

对于无状态(状态是指需要维护的上下文环境,用户请求依赖于这些上下文环境)的服务,采用集群就能很好的伸缩,增加系统的吞吐,比如挂载nginx之后的web server

对于有状态的服务,也有两种形式,每个节点提供同样的数据,如mysql的读写分离;每个节点只提供部分数据,如mongodb中的sharding

分布式存储系统中,partition(sharding)和replication(backup)都有助于并发。

绝大多数web server,要么使用多进程,要么使用多线程来处理用户的请求,以充分利用多核CPU,再有IO阻塞的地方,也是适合使用多线程的。比较新的协程(Python greenle、goroutine)也是一种并发。

惰性

将计算推迟到必需的时刻,这样很可能避免了多余的计算,甚至根本不用计算,参见:

http://www.cnblogs.com/xybaby/p/6425735.html

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(格子算法)也都是缩小解空间的方法。


性能优化与代码质量

很多时候,好的代码也是高效的代码,各种语言都会有一本类似的书《effective xx》。比如对于python,pythonic的代码通常效率都不错,如使用迭代器而不是列表(python2.7 dict的iteritems(), 而不是items())。

衡量代码质量的标准是可读性、可维护性、可扩展性,但性能优化有可能会违背这些特性,比如为了屏蔽实现细节与使用方式,我们会可能会加入接口层(虚拟层),这样可读性、可维护性、可扩展性会好很多,但是额外增加了一层函数调用,如果这个地方调用频繁,那么也是一笔开销;又如前面提到的C扩展,也是会降低可维护性、

这种有损代码质量的优化,应该放到最后,不得已而为之,同时写清楚注释与文档。

为了追求可扩展性,我们经常会引入一些设计模式,如状态模式、策略模式、模板方法、装饰器模式等,但这些模式不一定是性能友好的。所以,为了性能,我们可能写出一些反模式的、定制化的、不那么优雅的代码,这些代码其实是脆弱的,需求的一点点变动,对代码逻辑可能有至关重要的影响,所以还是回到前面所说,不要过早优化,不要过度优化。


总结

来张脑图总结一下

热门内容:如何设计一个牛逼的本地缓存
代码神器:拒绝重复编码,这款IDEA插件了解一下.....Spring 的 Bean 生命周期,11 张高清流程图及代码,深度解析
Lambda表达式入门,看这篇就够了!最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・

项目实践中的一些性能优化指南相关推荐

  1. 多迪php,多迪PHP项目经理深度解析:PHP应用性能优化指南!

    原标题:多迪PHP项目经理深度解析:PHP应用性能优化指南! 程序员都喜欢最新的PHP 7,因为它使PHP成为执行最快的脚本语言之一.但是保持最佳性能不仅需要快速执行代码,更需要我们知道影响性能的问题 ...

  2. Spark性能优化指南:基础篇

    前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作 ...

  3. Spark性能优化指南:高级篇

    前言 继基础篇讲解了每个Spark开发人员都必须熟知的开发调优与资源调优之后,本文作为<Spark性能优化指南>的高级篇,将深入分析数据倾斜调优与shuffle调优,以解决更加棘手的性能问 ...

  4. 转载 Spark性能优化指南——基础篇

    前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作 ...

  5. Spark性能优化指南——基础篇

    在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作,应用 ...

  6. Spark性能优化指南——高级篇

    继基础篇讲解了每个Spark开发人员都必须熟知的开发调优与资源调优之后,本文作为<Spark性能优化指南>的高级篇,将深入分析数据倾斜调优与shuffle调优,以解决更加棘手的性能问题. ...

  7. 【转】【技术博客】Spark性能优化指南——高级篇

    http://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651745207&idx=1&sn=3d70d59cede236e ...

  8. 移动H5前端性能优化指南[转]

    移动H5前端性能优化指南 米随随2015.01.23 移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用 2. 在Mobile侧我们提出三秒种渲染完成首屏指标 3. 基于第二点 ...

  9. php接口性能测试工具,PHP 应用性能优化指南

    原标题:PHP 应用性能优化指南 PHP代码优化最佳实践 编写好的PHP代码是创建快速稳定Web应用的关键一步.从一开始就遵循一些最佳实践技巧将节省后期填坑的时间. 1. 尽可能的使用PHP的内置方法 ...

最新文章

  1. 就想写个爬虫,我到底要学多少东西啊?
  2. linux常见协议对应端口(后期补充)
  3. 使用Spring Boot搭建你的第一个应用程序
  4. 查看Infinicon的HCA卡上的网络流量的方法
  5. MFC多线程各种线程用法 .
  6. mono linux c 服务,使用mono-service在Linux上包装Windows服务
  7. 通过ISO镜像文件安装Ubuntu(可实现默认启动Windows的双系统)
  8. 台式计算机计量单位,计算机的计量单位以及常见的数据类型
  9. java socket 报文解析_java socket解析和发送二进制报文工具(附java和C++转化问题)
  10. ios 支付宝支付 回调数据_iOS逆向支付宝
  11. Android微信跳一跳,自动跳App实现
  12. easyui datagrid 单选框 效果
  13. 中国最大照明企业贱卖给外资 创始人遭下狱 刘强东怒斥:有些人没有道德底线!...
  14. 多模态语义分析_学术竞赛 | 冠军方案分享2020科大讯飞AI开发者大赛多模态情感分析赛道...
  15. javawebJSP餐厅点餐系统源码JSP点餐系统JSP网上订餐系统JSP在线订餐系统JSP外卖系统
  16. livox_camera_calib的使用
  17. linux篇-图解cacti监控安装
  18. Windows 10 Conda 更换清华大学的镜像源
  19. AT指令详解,错误代码详解
  20. 操作系统-文件存储空间管理

热门文章

  1. centos7安装mongodb3.4
  2. Linq 等式运算符:SequenceEqual
  3. [Unity3D]Unity3D连衣裙实现游戏开发系统
  4. Centos和Redhat的区别和联系
  5. 文件解析库doctotext源码分析
  6. 二分图匹配匈牙利算法DFS实现
  7. Java 线程 笔记(1)
  8. 让你彻底明白什么叫游戏引擎(1)
  9. 【ACM】杭电OJ 2030
  10. 沉浸式导航栏html,混合开发 h5+ 沉浸式的适配