一、GC
1、基本概念
GC:在托管进程中存在两种内存堆(托管堆、本机堆)。
托管堆也就是GC堆,它又分为2种:小对象堆、大对象堆LOH,以85000字节分界。
小对象堆有3代,0、1、2。
对象总诞生于0,每次回收,还存在的对象,会提升一代。
0、1回收被称为 瞬时回收,0、1总是在同一个段中。
在回收时有可能进行 碎片整理,将空闲空间连续起来(在划算的时候)。
大对象堆LOH不会自动进行回收。
GC通过任一已知的根对象,层层访问到某个对象,那么它就是存活的。
2、垃圾回收
4个阶段:挂起、标记、碎片整理、恢复
3、服务器工作模式
服务器模式适合大型的服务端应用,比如 ASP.NET Core 程序。
服务器模式下 GC 的回收会尽量的延迟,从而减少停顿。为了获得更高的吞吐量与性能,程序会分配更多的内存。
服务器模式下 CLR 根据 CPU 核心数量来分配 GC 堆的数量。
单处理器的服务器模式是无效的
在项目里的app.config配置
4、低延迟模式
开启不会执行2代垃圾回收
如果你的项目,在进入某一功能模块时,不希望此时进行垃圾回收。
可采取:
进入低延迟模式前先执行一次完全垃圾回收,离开低延迟模式时也做一次完全垃圾回收。
5、GC优化
转瞬即逝:对象的生存期尽可能短暂;
池化:让对象尽快提升到2代中,确保回收发生在0/1代中;
永远不要使用终结方法(析构函数);
终结函数并不能保证会及时被执行。
从一个对象变得不可到达开始,到它的终结函数被执行,这段时间的长度是任意的、不确定的,有时不一定会被执行(gc只会执行一次)。
我们可以使用 try-finally 去显示的去释放资源。
尽可能避免在LOH分配内存;
6、垃圾回收通知
因为回收开销大,会对性能产生负面影响,你可以先停止程序的运行,等回收后在执行。
会返回1-99数字,越小,离下一次的回收时间越近。
二、异步编程
1、基本概念
每个处理器只能执行一个线程,在给处理器安排线程的时刻,Windows需要进行上下文切换。
如果线程被任何IO阻塞,则可能进入等待状态。
sleep可以主动进入等待状态,但会阻塞IO
无论什么情况,你都应该使用Task
将sleep 替换为 await Task.Delay(ms) 不会阻塞IO
2、Task
串行:你的一个函数需要等待上一个函数执行完,才进行。
上一个任务执行完成后自动启动下一个任务,实现任务的按序进行。
task.ContinueWith(xxx1).ContinueWith(xxx2);
3、并行循环
3.1、Parallel.For
简单的并行计算的方法。
在 Parallel.For中,不能使用与顺序循环中相同的 break 或 Exit 语句,这是因为这些语言构造对于循环是有效的,而并行“循环”实际上是方法,不是循环。
可以使用 Stop 或 Break 方法
Stop:尚未开始的循环的任何迭代都无需运行。 它可以有效地取消循环的任何其他迭代。 但是, 它不会停止已经开始执行的任何迭代。
调用方法会导致此IsStopped属性返回true到仍在执行的循环的任何迭代。 对于长时间运行的迭代特别有用, 它可以检查IsStopped属性为true提前退出。
Break:应运行当前迭代之后的任何迭代。 它可以有效地取消循环的任何其他迭代。 但是, 它不会停止已经开始执行的任何迭代。
例如, 如果Break是从0到1000的并行循环的第100迭代调用的, 则所有小于100的迭代仍应运行, 但不会执行从101到1000的迭代。
Stop和Break的区别就在于,Stop仅仅通知其他迭代尽快结束,而Break不仅通知其他迭代尽快结束,同时还要保证退出之前要完成之前的迭代。
3.2、Partitioner
把一个大集合,或大数组分成若干个区来执行
由于Parallel.For的开销过大,可以使用 Partitioner类,进行分区循环。
Partitioner.GetPartitions(number):会查看本机的cpu核心数,把分区的上限限制在核心数上,可以少于等于这个值;
多了没有cpu去运算,也没有意义了。
4、阻塞
lock和其他种类的直接线程同步方式,都是很明显的阻塞式调用。
反面教材,在异步方法里,不使用异步
5、async,await
当task结束时机不确定,或者使用多级task,就不在适用了,此时可以使用async,await。
注意:如果我们没有使用await 关键字,那么该方法就作为一个同步方法。编译器将向我们显示警告,但不会显示任何错误。
按照从不等待的原则,不使用Wait
Wait是一种同步方法,它会导致调用线程等待当前任务完成。 如果当前任务尚未开始执行,Wait 方法会尝试从计划程序中删除该任务,并在当前线程上内联执行该任务。 如果它无法执行此操作,或者如果当前任务已经开始执行,它会阻止调用线程,直到任务完成。
请勿创建大量的定时器
不能中止线程
当线程被强行中止时,它可能正执行某个关键操作的过程中,如文件操作、数据库访问或持有锁等。这可能导致数据不一致、资源泄露或其他不可预知的行为。
如果线程正在等待获取某个锁或同步,而被强行中止,那么它可能永远无法释放这些资源,导致死锁或其他同步问题。
线程可能拥有一些需要显式释放的资源(如非托管资源)。如果线程被中止,这些资源可能不会被正确释放,导致资源泄露。
volatile:值发生了变化,其他线程立马可见
指示一个字段可以由多个同时执行的线程修改
不会被线程调度或硬件优化所中断。使用volatile
关键字可以确保所有线程都看到该字段的最新值,而不是可能存在的本地缓存中的旧值。
WaitAsync:异步等待,逻辑上看是递归,但实则不是;
异步等待的时候可以指定一个 Timeout 时间或者一个取消令牌 CancellationToken
三、编码一般规则
避免装箱;
for要明显快一点,相比foreach;
类型转化,AS,如果不是会返回null,请勿先is,在as
异常消耗巨大,数据类型转换,使用TryParse,解析失败会返回bool
四、内存溢出问题
软件:PerfView、windbg、dotTrace
1、PerfView
1.1、收集
1.a启动方式:在工具栏选择Collect下的Run。
“Run”是直接指定需要启动的应用程序的名称,以便启动该程序。
Command输入exe全路径名称,并点击“Run Command”
1.b启动方式:在工具栏选择Collect下的Collect。
“Collect”则是直接启动Perfview并开始收集,
高级选项中Focus process可以输入PID, os Heap Process 输入PID。PID从任务管理器看。
Max Collect Sec 为启动多少秒,可以不填。
2、zip复选框:选中你可以把数据复制到其他电脑分析,不选会快一点,默认选中的;
3、在处理过程中,Perfview的右下角会闪动,并且可以查看运行日志。
4、在收集完毕后,会创建“PerfViewData.etl.zip”。
1.2、分析CPU
1、选择“CPU Stacks”,选择你想看的进程,双击进入堆栈图。以微信举例。
2、Metric/Interval: 0.01 如果值是0.01-0.02,那么查看CPU就不是好的选择;
when:代表时间范围,要确保数据是好的;
3、CallTree:树,自上而下,展示程序从一开始到结束时的堆栈数据。
从最上面100%的一层层打开,找到哪个函数占用性能多,在跳转到源码,进行优化。
4、 Fold% ,你可以输入数字,表示你不关心CPU少于多少的堆栈
表格里的列,代表的意思:
Exc% 表示使用了多少时间
Inc%表示谁用了最多的性能
5、By Name:查看同一名称的,占用了多少资源,可以选中它,鼠标右键,选择goto,选择F10,到CallTree中查看树。
在CallTree中,可以选中方法名,右键选择Goto source ,查看源代码
1.3、分析内存
1、Collect收集。
2、收集后,打开Memory下的Net OS Heap Alloc Stacks
3、清空GraupPats文本框内容,然后回车
4、与CPU分析一致