性能优化第1节课
一、第1节课
今日课堂目标
数据库SQL语句写的不够好,硬件不行了,都是我们的揣测。在我们没有进行排查之前,任何结论都是不可靠的。
1000条到100万条之间,问题不在SQL语句,也不在服务器环境,而在代码处理的逻辑上。
先从文件中读取1条数据,放在链表,接着在数据库中持久化这条数据。
问题出现在哪?出现在链表中了。如果放在链表中100万条数据,那么,每次向数据库中放入一条数据,都要遍历这100万条数据。
主要原因:应用程序的设计导致性能变差。
要想做好性能优化,需要了解一些性能优化的基础理论。
1、算法对性能的影响
(1)数组,时间复杂度O(n),100完个数据进行处理。
优点:适合循环处理,适合按顺序存放数据、查找数据。
缺点:如果事先不知道数组长度的话,那么就是占用过多空间,要么就是空间不够。插入数据也不方便。
(2)链表,像链条一样的数据结构。数据以及下一个箱子(节点)的位置或地址。复杂度O(n)。
访问链表中某一个节点A的下一个。
优点:灵活。插入数据,在插入数据后,把链表连起来就可以。
缺点:不能使用随机访问,只能走一遍。只要知道地址就可以随机访问。
(3)树(二叉树、N叉树、B树)
B树:尽量保持各分支平衡的多叉树。只在树的叶子节点存放数据。
在开发中,主要处理的数据是查询。查找相应的数据。树结构就是为了方便查找而创造出来的。
在树中放1000条数据和放100万个数据,层级高度,只差30倍。
访问多少次后能找到目标数据,1000个的话是32此,100万个数据就是1000此。
优点:可以不同检查无关的数据,查找树右边的话,左边的就可以不看了。范围会逐渐缩小。
缺点:数据更新不方便。如果总是放入相同的数据,会导致某一个特定位置的分支不断延伸,这样就不能发挥它的性能优势了。
(4)散列算法
使用这个算法,只需要一次计算就可以找出数据。散列算法的复杂度:O(1)
散列计算 用的是取余,余数。
1、5、9、13、16、27、38、102。
对这些数据进行取余,把这些数据当作下标来使用。
10个空间的数组,用10求余。依次下去,就可以知道0~9的下标。
18,28,用10求余得到8。该值称为散列值。
优点:不论数据量怎么增加,都可以在一定时间内完成查找和储存;有消除不平衡性的效果(针对相同的数据)。
缺点:散列值相同。
(5)队列
适用于按顺序来处理工作。
优点:[1]当有大量处理涌入时,可以先将其放入队列,按顺序处理。尤其访问人气很高的网站,需要一段时间才出现,这是因为放入到了队列里来处理。[2]用于多个系统之间的连接点,还可以作为缓冲。这种方式不适用于那些需要实时响应的数据,为了处理结果,需要重新访问你一次。这也是它的缺点。
明白数据结构与算法的优缺点后,在做项目的时候,就知道在什么情况下需要用哪种算法了。
在性能优化上,数据结构与算法是非常重要的基础知识。
2、缓存对性能的作用
CPU中也有缓存。缓存指的是为了提高性能而保存的东西。为了提高性能,把这些东西保存在比较近的地方。
缓存方式有两种:回写、直写。
回写:更新数据的时候,要考虑是否需要更新缓存。先更新缓存内的数据,再更新实际存放数据的地方。
回写的优点:不用等待写入数据实际存放的地方,所以速度很快。
回写的缺点:如果缓存中的数据丢失的话,那么,实际保存数据的地方内的数据就是旧的。
如果一定要确保再数据实际存放的地方保存,再更新缓存,就需要用直写的方式。
直写的优点:可以确切的保证数据的更新。
直写的缺点:速度慢。
3、锁
数据库中的锁:行锁、表锁。
编程语言中的锁、CPU命令级别的锁、数据库内部也有锁。
锁是在并行处理的情况下所必须的机制。
链表结构中,在1和4之间插入数据2,同时,另一个程序,想在1和4之间插入3。那么出现的结果就是,链表结构会被破坏掉。为了防止这种情况,在更新链表的时候,让别的程序等待。
锁,就是在某个处理进行期间起到保护作用的机制,是为了防止别的处理侵入。
如果你已经知道只有一个线程进行操作,那么不用锁也可以。
.NET是线程安全,是因为它的底层都有锁。
读取的时候也需要加锁。在读取一个值以后,同时有另外一个程序对这个值进行更新了。那么,读取的值就不正确了。这属于不可重复读取。
锁在等待的时候发生了什么事情?在性能测试和生产环境中,会出现多个请求同时涌入,那么,等待锁释放,就排起了一个长队,等待时间会以指数级别增长。
如何解决这个等待的问题?锁在进行保护的过程中,只要处理没有完成,就不能释放锁。最基本的解决办法就是“让受保护的处理尽快完成”。
如果是表锁,那么,就要让SQL尽快完成。减少占有锁的时间。
分割锁,把表锁换成行锁。这就是把锁分割了。
4、响应与吞吐
考虑性能的时候,还有两个重要的概念。响应与吞吐。
响应表示的是应答的快慢。
吞吐表示的是处理数量的多少。
响应就像是几乎装载不了什么东西,但速度飞快的赛车。
吞吐就像是速度很慢,但能装载大量货物的卡车。
一个人搬运一次货物需要10秒,2个人搬运一次货物仍然需要10秒。时间还是10秒,但是工作量增加了。在这里,工作量就是吞吐,时间就是响应。
在实际的环境中,有的系统是偏重响应的,有的系统是偏重于吞吐。偏重于响应是万金油的做法,响应变快的话,一般来说吞吐也会变大。但是就像CPU的时钟频率一样,和磁盘的IO都会有极限。物理上的增速是有限度的,硬件不能无限的提速。那么,这时,就需要偏重吞吐的系统。
热门的网站和APP,即使访问很集中,响应也不会变慢。这种擅长处理高并发的系统,是偏吞吐的系统。
PC和服务器比较,CPU的时钟频率并没有很大的差异,但是,价格相差上百倍。这样的原因,很简单。服务器为了能够支撑高并发,进行了优化。
所以,在考虑的时候,要明白你的系统是偏向于响应,还是偏重于吞吐。
5、性能分析工具
性能必须是可以测量的。如果不知道问题原因,就没办法解决。换句话说,性能问题的处理和性能调优都是从正确的测量开始的。
(1)VS中,帮助我们分析应用程序中的性能问题。
VS性能探查器,简单的测试是可以做到的。
对单个类进行分析。使用,在Main函数的开始、结尾设置断点,Deug版,(测试——>窗口——>诊断工具),启动调试。在诊断工具中,CPU使用率中有个“记录CPU配置文件”。
对整个项目进行分析。Release版。测试——>性能探查器,选择CPU使用率。可以诊断当前系统整体进行探查。外部代码:是系统代码,可以略过。企业版和专业版有。
(2)dotTrace,它是JetBrains出的一款.NET性能分析工具。收费。可以在不同维度进行分析,也支持.NET Core。有2种启动方式,四种分析模式。如果安装了的话,在扩展中可以找到。
四种分析模式:Sampling、Tracing(常用)、Line-by-Line、Timeline
Sampling:分析模式很快,分析的是总体性能,不够精确,可能会丢失一些数据。
Tracing:可以准确的测量方法被调用的数据(次数、时间)。
Line-by-Line:收集每一条语句的执行时间,但是也慢。使用范围:知道出现性能问题的大概位置在哪里
Timeline:少于10毫秒的方法就无法抓取到。
使用Tracing模式后,在出现的分析报告中,主要看User code。
VS性能分析与dotTrace性能分析,dotTrace看到的东西多,但不够直观。VS则相反。
性能探查器,就可以直接运行WEB项目。比如你去访问你一个接口,就会被性能探查器捕捉,停止项目。性能分析数据就会出结果了。
6、性能测试工具
Benchmark,是开源的.NET程序性能测试的组件。在NuGet中直接安装。
对方法进行基准测试。
Benchmark测试步骤:
(1)确定要运行几次
(2)评估Benchmark这个组建带来的额外开销
(3)热身
(4)测试
(5)测试结果(减去Benchmark测试带来的额外开销)
(6)生成报告
在方法上,表明[Benchmark]特性,直接运行即可。看标红部分即可。
性能优化,没有一个统一标准,有的只是一个理论或方法。