软件性能优化全景

软件性能优化全景

## 1、架构优化

### 负载均衡

负载均衡其实就是解决一个分活的问题,对应到分布式系统,一般在逻辑服的前面都会安放一个负载均衡器,比如NGINX就是经典的解决方案。负载均衡不限于分布式系统,对于多线程架构的服务器内部,也需要解决负载均衡的问题,让各个worker线程的负载均衡。

### 多线程、协程并行化

虽然硬件架构的复杂化对程序开发提出了更高的要求,但编写充分利用多CPU多核特性的程序能获得令人惊叹的收益,所以,在同样硬件规格下,基于多线程/协程的并行化改造依然值得尝试。

多线程不可避免要面临资源竞争的问题,我们的设计目标应该是充分利用硬件多执行核心的优势,减少等待,让多个执行流畅快的奔跑起来。

对于多线程模型,如果把每一个要干的活抽象为一个task,把干活的线程抽象为worker,那么,有两种典型的设计思路,一种是对task类型做出划分,让一类或者一个worker去干特定的task,另一种是让所有worker去干所有task。

第一种划分,能减少数据争用,编码实现也更简单,只需要识别有限的竞争,就能让系统工作的很好,缺点是任务的工作量很可能不同,有可能导致有些worker忙碌而另一些空闲。

第二种划分,优点是能均衡,缺点是编码复杂性高,数据竞争多。

有时候,我们会综合上述两种模式,比如让单独的线程去做IO(收发包)+反序列化(产生protocol task),然后启动一批worker线程去处理包,中间通过一个task queue去连接,这即是经典的生产者消费者模型。

协程是一种用户态的多执行流,它基于一个假设,即用户态的任务切换成本低于系统的线程切换。

### 通知替代轮询

轮询即不停询问,就像你每隔几分钟去一趟宿管那里查看是否有信件,而通知是你告诉宿管阿姨,你有信的时候,她打电话通知你,显然轮询耗费CPU,而通知机制效率更高。

### 添加缓存

缓存的理论依据是局部性原理。

一般系统的写入请求远少于读请求,针对写少读多的场景,很适合引入缓存集群。

在写数据库的时候同时写一份数据到缓存集群里,然后用缓存集群来承载大部分的读请求,因为缓存集群很容易做到高性能,所以,这样的话,通过缓存集群,就可以用更少的机器资源承载更高的并发。

缓存的命中率一般能做到很高,而且速度很快,处理能力也强(单机很容易做到几万并发),是理想的解决方案。

CDN本质上就是缓存,被用户大量访问的静态资源缓存在CDN中是目前的通用做法。

### 消息队列

消息队列、消息中间件是用来做写请求异步化,我们把数据写入MessageQueue就认为写入完成,由MQ去缓慢的写入DB,它能起到削峰填谷的效果。

消息队列也是解耦的手段,它主要用来解决写的压力。

### IO与逻辑分离、读写分离

IO与逻辑分离,这个前面已经讲了。读写分离是一种数据库应对压力的惯用措施,当然,它也不仅限于DB。

### 批处理与数据预取

批处理是一种思想,分很多种应用,比如多网络包的批处理,是指把收到的包攒到一起,然后一起过一遍流程,这样,一个函数被多次调用,或者一段代码重复执行多遍,这样i-cache的局部性就很好,另外,如果这个函数或者一段里要访问的数据被多次访问,d-cache的局部性也能改善,自然能提升性能,批处理能增加吞吐,但通常会增大延迟。

另一个批处理思想的应用是日志落盘,比如一条日志大概写几十个字节,我们可以把它缓存起来,攒够了一次写到磁盘,这样性能会更好,但这也带来数据丢失的风险,不过通常我们可以通过shm的方式规避这个风险。

指令预取是CPU自动完成的,数据预取是一个很有技巧性的工作,数据预取的依据是预取的数据将在接下来的操作中用到,它符合空间局部性原理,数据预取可以填充流水线,降低访存等待,但数据预取会侵害代码,且并不总如预期般有效。

哪怕你不增加预取代码,硬件预取器也有可能帮你做预取,另外gcc也有编译选项,开启它会在编译阶段自动插入预取代码,手动增加预取代码需要小心处理,时机很重要,最后一定要基于测试数据,另外,即使表现很好,但代码修改也有可能导致效果衰减,而且预取语句执行本身也有开销,只有预取的收益大于预取的开销才是值得的。

累啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦,写不动啦啦啦!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!哪天有空再写后面的章节吧

## 2、算法优化

### 哈希(HASH)

#### 哈希和字符串比较 

#### HashMap

#### 哈希和平衡搜索树的比较 

### 基于有序数组的二分查找

### 数据结构的实现优化

### 延迟计算 &  写时拷贝

### 预计算

### 增量更新

## 3、代码优化

### 内存优化

小对象分配器

内存分配和对象构建分离

### cache优化

i-cache优化、d-cache优化、cache对齐、结构体重排

### 判断前置

### 整体操作替代小操作

### 复用

### 减法

#### 减少冗余

#### 减少拷贝、零拷贝

#### 减少参数个数(寄存器参数、取决于ABI约定)

#### 减少函数调用次数/层次

#### 减少存储引用次数

#### 减少无效初始化和重复赋值

### 循环优化

### 防御性编程适可而止

### release干净

### 慎用递归

## 4、编译优化

### inline

### restrict

### LTO

### PGO

### 优化选项

## 5、其他优化

### 绑核

### SIMD

### 锁与并发

#### 锁的粒度

#### 无锁编程

#### Per-cpu data structure & thread local

#### 内存屏障

小结

性能优化是一项细致的工作,工程师们曾致力于寻找一劳永逸解决性能问题的捷径,但遗憾的是,没有银弹,但这并不意味着性能优化无章可循。软件工程师们在性能优化方面积累了大量的经验,包括架构、缓存、预取、工具、编译器与编程语言,代码重构等实践经验方方面面,这些方法和探讨都具有借鉴意义。

性能优化也是一个系统性工程,出现性能瓶颈再优化是一种先污染后治理的思路。更好的方式是将性能贯穿于软件的整个生命周期之中,在设计之初即把性能作为一项需求甚至关键目标加以考虑,开发中持续监控性能的变化并严格遵从高性能编码规范,后期维护将性能纳入维护体系。

严格的说,性能优化和性能设计有所不同,性能优化通常是在现有系统和代码基础上做改进,它并非推倒重来,考验的是开发者反向修复的能力,而性能设计考验的是设计者的正向设计能力,但性能优化的方法可以指导性能设计,两者互补。

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91326 人正在系统学习中

来源:weixin_37097700

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年11月26日
下一篇 2020年11月26日

相关推荐