Linux内核性能剖析的方法学和主要工具
计算机科学的先驱DonaldKnuth(高德纳)曾经说过:过早的优化是万恶之源,更详细的原文如下:Weshouldforgetaboutsmallefficiencies,sayabout97ofthetime:prematureoptimizationistherootofallevil。Yetweshouldnotpassupouropportunitiesinthatcritical3。Agoodprogrammerwillnotbelulledintocomplacencybysuchreasoning,hewillbewisetolookcarefullyatthecriticalcode;butonlyafterthatcodehasbeenidentified。它向我们揭示了一个道理,我们应该首先定位到那3真正成为瓶颈的代码,而忽略97那些smallefficiencies,所谓将军赶路,不打小鬼,这是我们进行一切性能优化的前提。因此,剖析(profiling),成为了性能优化中最重要的环节之一。
性能剖析,要求我们的思维方式主要是topdown的,我们能全局地从顶部向下的看问题,这就像一个全科医生,出了问题后,能大致估摸出一个方向知道是哪个器官可能出了问题。但是,我们同时也必须具备downtop的能力,正如一个专科医生,能看到细小器官的癌细胞最终会怎样向全身发散,从而危机到人的健康和生命。
系统的性能优化不太是一个通过review几千万行代码,发现问题,然后更正问题优化的过程。而更多是一个通过某些剖析手段,把系统当成黑盒子,暴露数据,topdown地看这个系统,在发掘问题后,再深入到白盒downtop的过程。《魏略》曰:亮在荆州,以建安初与颍川石广元、徐元直、汝南孟公威等俱游学,三人务於精熟,而亮独观其大略。性能优化本身,是一个从统帅诸葛亮逐步变身单兵战神吕布的过程,而你首先必须是统帅。
一、性能剖析的总体认识
下面我们也来一个观其大略的环节,先不求甚解地看一看内核性能分析关注的指标,分析角度和一些其他基本常识。
吞吐
吞吐强调单位时间里可以做多少有用功。比如,我们会用netperf来评估网络的带宽;用sysbench来评估MySQL的QPS(QueriesPerSecond)和TPS(TransactionsPerSecond);用vmscalability来评估Linux内存管理的吞吐性能等;用tbench来评估内核调度器wakeup路径上的优化是否有效等。
为了提高吞吐,我们常常采用的一个方法是横向拓展硬件或者软件的规模,比如增加更多的CPU、使用多线程等。然而世间万物,不如意者十之八九,不是你爱她多一点,她就一定会爱上你。吞吐的拓展受限于著名的Amdahlslaw和UniversalScalabilitylaw(USL)。
按照USL,核多(核数为p),实际的加速倍数是:
p增大的时候,不仅仅是分子增大,分母也增大,分母因子随着p线性增大,k因子随着p的平方线性增大。USL的分母中去掉kp(p1)就是Amdahlslaw,所以Amdahlslaw并没有USL完整准确。
其中的系数是contention(核间、多线程间因为竞争等锁、同步等而不能并行执行),k系数是coherency(核间、多线程之间的协同形成共识的开销)。系数比较好理解,比如两个CPU访问同一个链表,他们需要竞争锁,假设平均1秒里面0。1秒在等锁,则2个cpu实际只有20。91。8秒在做有用功,而不是2。0。k系数相对难理解一点,比如我们在CPU0释放一个spinlock,在ticketspinlock里面,这个spinlock的新值要通过cache同步网络同步给系统的每个CPU形成所有CPU对这个新值的一致性理解,这个cache同步的开销很大,而且随着p的平方而增大。这就是为什么内核针对spinlock不断在进行优化,比如从ticketspinlock变成qspinlock,其实是减小了需要coherency的CPU个数。
举一个栗子,软件的童鞋很可能会天真地以为内核的atomicinc()、TLBflush之类的操作是非常便宜的,其实它们都有严重的k因子问题,就是coherency开销。如果做一个简单的操作:
lcaseA:100核,100个线程同时做atomicinc(),做一秒钟;
lcaseB:10核,10个线程同时做atomicinc(),做一秒钟。
caseA原子操作的次数不会是caseB的10倍,它实际远小于10倍,比如实测结果可能是吓死你的4倍(当然每个具体的SoC都可能不一样),等于10倍的硬件目前这个世界还没有做出来,未来也造不出来。
这些系数、k系数对服务器的影响,远大于对桌面和手机等系统,因为服务器上的p特别大。所以,长期困扰服务器的问题,比如我从50个cpu变成100个cpu,MySQL的吞吐一定增加了一倍了吗?不好意思,很可能只是吓死你的1。3倍,如果运气不好的话,还可能倒退。
再回到spinlock,大量的文献显示,由于ticketspinlock等在核间coherency上的巨大开销,许多业务的性能可随着CPU核数量的增大而减小【1】。
最开始核增加的时候,相关业务的性能在提升,到某个拐点后,再增加更多的CPU,性能不升反降,出现了collapse。
延迟
甲骨文的山,是一个象形字,它较好地贴合了Linux世界里的延迟模型。Linux世界的延迟往往呈现为这种multimodal(有多个峰值而不是只分布在一个峰值周围)或者两极分化特性。
由于这种multimodal分布的存在,这个时候,我们描述平均值的意义其实不是特别大。比如一个班上有30个学生,其中10个人90分以上(学霸),还有10个人50分以下(学渣),另外还有10个在5090分之间。我们说这个班的学生平均分60分,其实没有任何意义,拉马老师来和我们平均,没意思的。这个时候,我们需要直方图来描述这种分布,从而更加直观地看出来这个班的学霸和学渣比率。
如果我们想看一个真实的Linux例子,下面是我的PC在运行sudocatdevnvme0n1devnull的过程中,我看到的BIO(blockIO)的延迟分布:
我们看到了2个峰值,一个峰值在64us127us之间;另外一个峰值,则围绕着10242047us分布。当然,还有一个值会偏移地特别远,比如有2个采样点,落在了131072us262143us之间。那2个离群很远的值,我们一般也称呼它们为outlier,它们是夜空中最闪亮的星,天生不是凡人。
种种迹象表明,我们仅关注平均值的意义非常有限。对于偏移中线的部分,在延迟分析领域,我们还特别关注一个非常重要的概念,taillatency,中文可译为尾延迟。比如我们说,90的延迟落在1ms以内,99的延迟在10ms以内,但是还有1的延迟可能更大,甚至形成一个很长很长的尾巴,可能有的达到了1秒也说不定。在延迟分析领域,我们很可能关注这些尾部,比如大家一起竞争mutex,那些延迟很大的case,可能会形成手机系统的卡顿,因此丢帧。对于服务器、电商、云服务等领域而言,高的尾延迟,会直接影响到企业的revenue。
延迟的几个主要可能的来源:
1。进程实际可以运行(TASKRUNNING),但是由于调度延迟的原因抢不到CPU;
2。进程同步等待一个IO动作的完成,这些IO动作可能是syscall的readwrite,也可能是mmap内存pagefault后的IO;
3。系统内存吃紧,进程陷入directmemoryreclaim,直接回收内存;
4。进程等其他进程释放锁,这里又分2种可能性
a。等锁队列比较长,比如等mutex、spinlock,前面已经挂了一个连在等,等到自己的时候,心已经碎了;
b。等锁队列可能不长,但是持有锁的进程遭遇了情况1、情况2和3,导致长期不放锁。类似你在等厕位,他却坐马桶上看了场电影。
所有的上述不确定情况,都可能形成不确定的taillatency。我们需要某些手段把它剖析和呈现出来。
功耗
内核有cpufreq,cpuidle,意识到功耗的调度器等。这些都致力于在降低功耗的情况下,总体不降低性能。除这些以外,我们也应该认识到,降低内核本身的CPU利用率,比如内存compaction、内存swapreclaim、锁自旋等的开销,也能进一步降低功耗。在一个内存受限的系统中,我们不能低估内核本身的开销所引起的功耗增加。
比如,我在qemu上ARM64Linux5。19rc2内核,然后运行下面简单的程序:
这个程序申请了1GB内存,然后fork出来64个进程,其中最原始那个父进程不停读写这个1GB的内存。代码gcc编译结果a。out。系统的内存是900M,并开启了zRAM交换功能。运行起来后,我们看它的CPU消耗,a。out固然是很大,可是kswapd0这个内核线程也是非常大的CPU占用。
kswapd0相对我们的有用功a。out,只是辅助的工作,本质属于浪费电。此外,我们的a。out本身占用的接近100的CPU,也主要耗费在a。out自身的动作上吗?这个时候我们也有兴趣看一下,我们perftopp一下,我们发现a。out也有大量的时间落在了内核的各种函数里面:
所以从性能分析的角度来说,我们也要把这些红框部分挖掘出来进行分析优化,因为本质他们也是纯耗电,属于overhead而不是realwork。
90分到100分特别难
在所有的性能优化领域,我们都不得不正视一点,无论你多么地不愿意:就是前期的优化是相对比较容易的,越到后来越难。一个考10分的学渣,也许经过努力比较容易考到60分,再继续挑灯夜战,可能也能考到90,但是哪怕本着只要学不死,就往死里学的极限热情,他也不一定能从90分考到100分,当然它可能考到91分。
努力到一定程度后,有没有可能越努力成绩越差呢?我觉得是可能的,因为UniversalScalabilitylaw(USL),学麻了容易走火入魔,反而出现性能的collapse。
成年人最重要的心理素质是学会和自己的平凡和解,打牌输了不要赖着不走再打一局不如隔天换个场子打。性能优化也是一样的,在某个角度已经搞到了91分,这个时候继续钻牛角尖的代价可能就比较大了。也许我们可以换另外一个角度,来做个从30分到91分的过程,最终实现总成绩9191182分,而不是10030130分。
在总成绩182的情况下,再去追求总成绩200,心态和效率都高很多。
offcpu和oncpu分析同样重要
当我们分析代码在CPU的耗时花费在哪里的时候,我们关心系统的oncpuprofiling,但是,当我们关注延迟等问题的时候,我们不仅要关注oncpuprofiling,更多的时候,我们需要关注offcpuprofiling。offcpuprofiling的目的在于全面地评估进程不在CPU上面跑的时候,因为什么原因被调度出去。offcpuprofiling完整地打印出进程离开CPU的原因的调用栈和时间分布,比如offcpu发生在等锁(如mutex和rwsem)、等IO完成、被调度抢占等情况。
性能profiling应同时着眼于oncpu和offcpu这两种情况。这个和优化我们的工作效率是一样的,我们既要上班时候尽可能降低CPU消耗,oncpu的时候少做纯耗电的无用功;也要看看是什么原因引起我们上班的时候钓鱼划水,把引起我们offcpu的原因分析出来从而减少摸鱼。
二、oncpu分析
oncpu分析主要着眼于2个点:一是找到占用CPU大的热点代码;二是提高单位运行时间内,CPU执行有效指令的条数。前一个点重心在于降低CPU利用率,后一个点则着重于提高CPU的工作效率。下面层层展开,展开过程中会直接介绍相关的工具。
oncpu火焰图
我们看到前述代码中,kswapd0占据了57。7的CPU。所以我们现在特别感兴趣,它的CPU的具体走向,火焰图可进行一个比较完整的呈现。假设kswapd0的PID是54,下面我们抓取内核线程54的信息:
我们把采样到的数据,通过火焰图工具进行绘制:
我们得到如下火焰图:
火焰图上我们发现,kswapd的时间有小部分发生在swapwritepage向zRAM写被替换的页面,而大部分发生在判断页面是否被访问的folioreferenceone(),以及页面向zRAM写之前unmap这个页面后的ptepclearflush()这2个动作上面。
你也许会想到要去减少folioreferenceone()和ptepclearflush()上面的开销,这是一个剖析和发现的过程。
CPU消耗比例分布
火焰图固然呈现了相关函数的CPU比例,但是,很多时候我们生成报告,尤其是向社区发patch,我们需要发送文字版的优化报告,这些报告可以突出CPU热点在哪里。这个时候,我们可以用perfreport的功能。
啥也不说了,盯着perfreport里面排名第1,第2的整起来。相关的这种数据报告在Linux社区非常常见,比如MGLRU的patch【2】里面就列出了MGLRU中某一特性优化前后的CPU消耗对比数据:
我们看到属于overhead的pagevmamappedwalk()的减小,但是属于lzo1x1docompress()的realwork的增大。所以,我们看数据说话,没有数据支撑的性能优化,是很难形成任何说服力的。这也就是为什么我们在内核社区发性能优化的patch,必然会被要求大量benchmark数据支撑。
最后一公里
前面我们发现trytounmap()后调用的ptepclearflush()是热点函数,但是它热在哪里,具体热在哪一行代码呢?这个时候,我们需要进一步perfannotateptepclearflush。
从annotate的结果可以看出,至少,在笔者运行的qemu平台上,tlbi附近的开销是非常大的,其他的代码开销几乎可以忽略不计。当然,真实的ARM64硬件上,tlbi也绝对不便宜。
中断屏蔽的问题
在一个类似如下spinlockirqsave()、spinunlockirqrestore()的区间里,由于采样的中断都是被屏蔽的,所以中间perf的采样结果,都会在spinunlockirqrestore()开启中断的这一刻爆出来,导致ARM64平台下,采样的热点落在类似spinunlockirqrestore()这样的地方。这是不准确的。
我们在调试时,可以使能ARM64PSEUDONMI选项,并传递irqchip。gicv3pseudonmi1这样的bootargs参数。这样,内核会用GIC的高优先级中断模拟NMI,在localirqdisable()、spinlockirqsave()、spinlockirq()这样的API里面只是屏蔽低优先级中断。
举一个典型的栗子,ARM64IOMMU(SMMU)的mapunmap开销大,导致dmamapsinglesg,dmaunmapsinglesg这些APIs在开启IOMMU的情况下,吞吐率不高。这个时候,我们通过前面的火焰图、CPU利用率分布报告很可能已经抓到了热点在driversiommuarmarmsmmuv3armsmmuv3。c【3】的armsmmucmdqissuecmdlist()这个函数,但是这个函数长成这个样子,进去的时候就关中断,出来的时候才开中断:
这个时候,你不开启irqchip。gicv3pseudonmi1是不可能perfannonate出来这个函数哪句话是热点的,所有热点都会落在末尾的localirqrestore()这句话。但是开启后,你才会抓到真正的热点:
CPU执行效率topdown分析
CPU利用率相等的情况下,执行效率是不是一样的呢?比如都是一秒里面干0。5秒的活,CPU利用率50,干出来的活是一样多吗?不是的,现代计算机系统普遍采用多发射、流水线、分支预测、预取、乱序投机执行等各种复杂机制,这使得同样时间段内能实际执行的有效指令条数,会是可变的。有两个非常重要的概念值得所有人关注:CPI:(CyclesPerInstruction)每个指令要多少周期。IPC:(InstructionsPerCycle)每个周期执行多少指令。
CPI和IPC为反比例关系。在同样CPU利用率的情况下,我们追求尽可能高的IPC。比如1个代码跑起来IPC是2。0,另外一个1。0,那么意味着同样的时间段,前者执行的指令是后者的2倍,CPU执行指令更顺畅,stalled(被添堵、处理器空转)的环节更少。
比如我在我的PC上面运行ls,可以看到IPC是0。9:
比如运行gccmain。c,可以看到IPC是1。36:
AhmandYasin在它的IEEE论文《Atopdownmethodforperformanceanalysisandcounterarchitercture》中,革命性地给出了一个从CPU指令执行的顺畅程度来评估和发现瓶颈的方法,允许我们从黑盒的角度以诸葛孔明隆中对式的格局来看问题。
现在处理器,一般在4个方面占用流水线的时间,而topdown方法,可以黑盒地呈现软件在CPU上面运转的时候,CPU的流水线究竟在干什么。FrontEndBound(前端依赖):处理器的前端主要完成指令的译码,把获取的指令翻译为一系列的microops(ops)。当CPUstalled在FrontEnd,通常意味着CPU在取指慢(比如icachemiss、解释执行等),或者复杂指令的翻译过程由于opscache不命中等原因而变地漫长。FrontEndstalled多,意味着前端无法及时给后端喂饱ops。目前主流的x86处理器,每个cycle可以给BackEnd喂4个指令,如果BackEnd也及时执行的话,IPC最高可达4。0。BackEndBound(后端依赖):处理器的后端主要完成前端喂过来的ops的执行,执行的过程可能涉及读写操作数(loadstore)、对操作数进行加减乘除各种运算之类。BackEndBound又可再细分为2类,corebound意味着软件更多依赖于微指令的处理能力;memorybound意味着软件更加依赖CPUL1~L3缓存和DRAM内存性能。当CPUstalled在BackEnd,通常意味着复杂运算指令延迟大,或操作数从memory(包括cache和DDR)获取的延迟大,导致部分pipelineslots为空(stall)。Retiring:ops被执行完成,最终的retire动作,提交结果到寄存器或者内存。BadSpeculation:处理器虽然在干活,但是投机执行的指令可能没有用。比如分支本身应该进else的,预测的结果却进了if执行错误的分支,虽然没有stall,但是这些错误分支里面的指令实际白白执行了不会retire,所以也浪费了pineline的时间。这里有一个基本常识,比如if(a)dox;elsedoy;,处理器并不是等待a的判决结果后,再去做x或者y,而是先根据历史情况投机执行x或者y,当然这个投机有可能出错。
注意FrontEndBound、BackEndBound和我们平时说的软件是CPUbound还是IOBound是相似概念,比如CPUbound的软件更加依赖计算能力并对CPU性能敏感(比如编译Linux内核、编译Android),IObound的软件更加依赖于IO动作本身并对IO性能敏感(比如你把硬盘ddifdevsda1ofxxx到xxx地方去)。
我们把CPUpipeline上的任何一个硬件资源想象成一个pipelineslots,假设CPU可以同时处理4条ops,下图共有40个slots,如果所有slots都做有用功,ops都能retiring,则IPC为4。0:
假设其中的20个slots要么是empty没活干(stalled),或者做的是无用功(分支预测错误等情况),那么它的IPC可能就是2。0了。
Intel处理器架构下,已经将topdown完全地工具化,有专门的topdown工具,有的甚至已经图形化了,比如VTuneProfiler里面就有类似功能【4】,可以具体化到每一个特定函数的FrontendBound、BackEndBound、BadSpeculation、Retiring等的情况:
大家从上表可以看出,基本retiring的比例越低,证明pipelineslots的stall越大,CPI也就越高(IPC越低)。比如,refreshpotential()的CPI高达3。589,主要是它的BackEndBound严重,其中MemoryBound高达73。2,所以优化这个函数,要多从cache命中率(L1,L2,L3)、DDR带宽延迟角度考虑。而优化sortbasket()函数则要多从分支预测优化角度考虑,因为它的BadSpeculation高达50。4。
有的童鞋对BadSpeculation可能还是不太明白,下面我们通过一个代码栗子:
这个里面有个随机数rrand(),会极大地破坏分支预测的准确性,所以topdown的结果如下:
如果我们把里面的rand()函数变成自己的版本,让分支结果并不那么随机:
再次topdown,结果则是(retiring很大,绿色友好程序):
此时的IPC也很大,达到3。49,比较接近4。0了:
Intel方面,脚本化的工具则有pmutools【5】。比如笔者在自己的X86PC(内存24GB)上面开启zRAM后运行如下代码:
假设kswapd的PID是202,我们捕获以下kswapd的bound情况:
由此我们可以看出,在上述场景下,kswapd跑起来主要是一个BackEndBound,其中BackEndBound里面的memory和corebound各占25。1和19。3,至于memorybound的部分,它又可以细分到L1,L2,L3cache更深层次的原因。
我们现在觉得memorybound是上述场景下kswapd的大头,我们实际也可以采样下kswapd的cachemisses。简单运行sudoperftopecachemissesp202命令看一下,cachemisses率最高的是LZ4compressfastextState()、memseterms()和isolatelrupages()。你也许可以怎么去优化这些函数,从而减小它们的BackEndbound的部分,提高kswapd的IPC。
Intel的平台享受上面的工具化优势。其他平台的童鞋也不必懊恼,perf本身也具有topdown能力,perfstat后面有个参数是topdown:
查看Intel童鞋的这个patch【6】,最新版本的perf实际也可以支持tdlevel2这样更细粒度的topdown打印:
整个oncpu分析的过程是topdown的,过程中的某些步骤也是topdown的,我们用一个流程图来描述:
三、offcpu分析
offcpu分析更多关注延迟问题,所以我们首先要获知延迟的分布,这个时候我们最好使用直方图。之后,我们可以过度到用offcpu火焰图等进一步分析offcpu在等什么,而在lockcontention的场合,则可以使用perflock来进一步进行锁的分析。
直方图
直方图深入人心,哪怕什么工具都没有,纯粹地用Linux内核也可以划出直方图。这个功能位于菜单tracerHistogramtriggers,通过内核tracepoints实现。
下面我们随便举个例子,看看mmvmscan。c中shrinkinactivelist()一般回收page个数的分布。注意,这纯粹是一个栗子,不对应任何的实际工程。
在直方图中,我们关心2个点,一个是key,一个是value。比如我们以回收页面的个数为key,回收到这一key值页面个数的次数为value。
我们在includetraceeventsvmscan。h和mmvmscan。c中增加这个trace:
tracemmvmscannrreclaimed(nrreclaimed,1);这句话表示回收nrreclaimed个页面的次数增加1。
现在我们使能tracer,以nrreclaimed为key,times为value,按照times降序排列:
运行系统,触发一些内存回收动作,采样到一些值后,直接读取直方图:
从直方图可看出,实验场景中,回收到32个pages的机会最多,占据76598次,其次是回收到1个、0个和2个的。
下面我们把所有采样复原,改为依据nrreclaimed升序显示:
复原:
开启新的采样:
运行系统,触发一些内存回收动作,采样到一些值后,直接读取直方图:
有的童鞋说,我现在关注的是延迟时间的分布,不是nrreclaimed。这完全不影响我们的原理,比如latency单位是us,我想搜集05ms,510ms,1015ms,1520ms各个档次的分布,我只需要trace:
tracexxxsysyyylatency(latency5000,1);
后面在hist中,05ms会是一行,510ms会是一行,依此类推。另外,内核里面统计延迟可用类似的代码逻辑(来源于kerneldmamapbenchmark。c的mapbenchmarkthread函数):
eBPFBCC中本身内嵌多个直方图工具,可满足许多常见的生活必须,比如之前演示的biolatency,还有这些已经自带:
bitehist。py:BlockIOsizehistogram。
argdist:Displayfunctionparametervaluesasahistogramorfrequencycount。
bitesize:ShowperprocessIOsizehistogram。
btrfsdist:Summarizebtrfsoperationlatencydistributionasahistogram。
cpudist:SummarizeonandoffCPUtimepertaskasahistogram。
dbstat:SummarizeMySQLPostgreSQLquerylatencyasahistogram。
ext4dist:Summarizeext4operationlatencydistributionasahistogram。
funcinterval:Timeintervalbetweenthesamefunctionasahistogram。
runqlat:Runqueue(scheduler)latencyasahistogram。
runqlen:Runqueuelengthasahistogram。
xfsdist:SummarizeXFSoperationlatencydistributionasahistogram。
zfsdist:SummarizeZFSoperationlatencydistributionasahistogram。
funclatency这个笔者也经常用,比如看一下vfsread()这个函数的执行时间分布,只需要把函数名加在funclatency之后就好:
我们看到vfsread()在实验场景,一般延迟是816us之间占据第一名。但是偶尔也能大到惊人的4294967296ns,也就是4。2秒,从直方图最后一行可以看出,这些可能就是outlier了。
eBPFBCC可以依附于kprobe、tracepoints上,在eBPFBCC上定制直方图,只用写非常简单的脚本即可,因为直方图是其本身内嵌的功能。比如在patch【7】中,笔者想知道kernelschedfair。c的selectidlecpu()在进程被唤醒的时候,统计选中与target同clusteridleCPU,不同clusteridleCPU和没找到idlecpu的比例,只需要写一个简单的脚本:
这个脚本的关键是涂了红色的三行,定义了一个直方图对象dist,然后就在里面通过dist。increment(e)增加采样点,最后通过b〔dist〕。printlinearhist(idle)把直方图画出来:
eBPFBCC里面有许多直方图的例子,我们定制自己的直方图的时候,依葫芦画瓢就好。
offcpu火焰图
实际上,eBPFBCC的代码仓库已经写好了offcpu工具,可以直接拿来用:
https:github。comiovisorbccblobmastertoolsoffcputime。py
它的原理是抓捕内核进行进程上下文切换的时间和backtrace,比如可以对finishtaskswitch()内核函数施加探针。因为我们在这个点可以知道什么进程被切换走了,什么进程被切换回来的,结合这些点的backtrace搜集,我们就可以得到睡眠和唤醒的调用栈,以及时间差。可以在这个函数加tracepoint,但是没有tracepoint的情况下,我们也可以直接attachkprobe探针。
finishtaskswitch()的函数原型是:
可见参数prev是被切换走的进程,而我们通过current可以拿到当前的进程,也即我们切入的进程。通过下面的算法,即可求出整个offcpu区间的时间和backtrace:
通过在内核finishtaskswitch()的kprobe点上插入eBPFBCC代码来完成这个算法,这样不需要修改和重新编译内核。之后,我们可以把eBPFBCC捕获的数据,借助flamegraph工具,绘制出offcpu火焰图。
下面给一个非常简单的案例,在我的Ubuntux86PC上面运行以下代码,创建32个进程,每个进程申请1G内存,然后循环执行:16字节对齐的时候写入一个字节,1MB对齐的时候睡眠30us。
测试内核是5。11。049generic,内核未开启抢占,但是允许PREEMPTVOLUNTARY:
CONFIGPREEMPTNONEisnotset
CONFIGPREEMPTVOLUNTARYy
CONFIGPREEMPTisnotset
测试的环境开启了zRAM的swap,但是关闭了磁盘相关的swap:
捕获a。out30秒的offcpu数据,并绘制火焰图。
得到如下火焰图:
从以上火焰图可以看出,a。out的延迟主要是3个方面原因:发生在其用户态代码本身调用的nanosleep()上;发生在pagefault的处理上;时间片到期后,timer中断进来,把它切换走。
我们把这3个块分别画3个圆圈:
火焰图的纵向是backtrace,横向是每一种情况的offcpu时间,横向越宽代表这个调用stack上的offcpu时间越久。
假设我们shrinkinactivelist()这个函数特别感兴趣,则可点击shrinkinactivelist()这个函数,单独查看这个函数的offcpu细节,我们发现,它其中一半的offcpu是因为它自己调用了一个msleep(),还有很大一部分发上在它主动call了condresched(),然后CPU被别人抢走;如果我们关注mutexlock()的延迟,则显然发生在shrinkinactivelist()shrinkpagelist()addtoswapgetswappages()mutexlock()这个路径上。
如果我们把焦点移到kswapd,我们还是运行上面的a。out代码,但是我们捕获和分析的对象换为kswapd。捕获kswapd的offcpu数据30秒并绘制offcpu火焰图。
绘制出来的kswapd的offcpu火焰图如下:
可见多数延迟发生在kswapd各种路径下(比如shrinkinactivelistshrinkpagelist路径)主动调用condresched()出让CPU,还有一部分延迟发生在最右边的shrinkslab的i915显卡驱动的slabshrink,点击放大它:
从上图可以看出,我们在内存回收shrinkslab()的时候,被i915驱动的i915gemshrink()堵住了,而i915gemshrink()被一个mutexlockinterruptible()堵住了,所以i915驱动持有的一个mutex实际上给shrinkslab()是添堵了。
特别有意思的是,这个offcpu火焰图,还可以变成双层的offwake火焰图。比如A进程等一个锁睡眠了,B进程是持有锁的人,B唤醒了A,在offwake火焰图上,它会以双层调用栈的形式进行展示。比如在a。out引起匿名页频繁swap的情况下,抓一下kswapd的offwake火焰图:
得到的图如下:
从图上可以完整看出,kswapdoffcpu的原因和唤醒者。比如画红圈的区域,kswapd因为调用kswapdtrytosleep()而主动进入睡眠,a。out在swapin的过程中doswappage()因而需要allocpages()的时候因为申请内存的压力,唤醒了kswapd内核线程。天蓝色的是kswapd,淡蓝色的是唤醒者。唤醒者的调用栈是从上到下,offcpu的kswapd的调用栈是从下到上,中间通过灰色隔离带隔开。这种描述方式,确实看起来比较惊艳有木有?
LockContention分析
在使能内核CONFIGLOCKDEP和CONFIGLOCKSTAT选项的情况下,我们可以通过perflock来进行lock的contention分析。其实perflock主要是利用了内核一系列的锁的tracepoints,比如tracelockacquired(lock,ip)、tracelockacquired(lock,ip)、tracelockrelease(lock,ip)等。
在我运行一个匿名页频繁swapoutin的系统里,抓取lock情况:
然后生成报告:
看起来rqlock、ptlockptr(page)、lruveclrulock的竞争比较激烈。尤其是lruveclrulock,由于contention比较多,totalwait时间比较大。这里要特别留意一点,lockcontention不一定是offcpu的,可能也有oncpu的,对于mutex,rwsem更多是offcpu的;spinlock,则更多是oncpu的。
对于offcpu以及相关的延迟问题,我们需要通过直方图获知延迟分布、offcpuoffwakeup火焰图获知offcpu的原因和唤醒者,如果是锁竞争的情况,则进一步通过内核perflock剖析锁竞争。
四、总结
性能优化经常是一个全栈的工作,对工程师的要求也比较高。它是一个很难用一篇文章完整描述清楚的话题,所以本文更多只是起一个提纲挈领的作用,许多话题有待以后有机会进一步展开。文中疏漏,在所难免,还请读者朋友海涵。最后推荐给亲爱的读者朋友们2本书:BrenDanGregg的《SystemPerformanceEnterpriseandtheCloud(SecondEdition)》;DenisBakhvalor的《PerformanceAnalysisandTuningonModernCPUs》
参考文献【1】https:people。csail。mit。edunickolaipapersboydwickizerlocks。pdf【2】https:lore。kernel。orglinuxmm20220614071650。2060648yuzhaogoogle。com【3】https:git。kernel。orgpubscmlinuxkernelgittorvaldslinux。gittreedriversiommuarmarmsmmuv3armsmmuv3。c【4】https:www。intel。comcontentwwwusendevelopdocumentationvtunecookbooktopmethodologiestopdownmicroarchitectureanalysismethod。htmltopdownmicroarchitectureanalysismethodGUIDFA8F07A135904A91864DCE96456F84D7【5】https:github。comandikleenpmutools【6】https:lore。kernel。orgall1612296553219629gitsendemailkan。lianglinux。intel。com【7】https:www。spinics。netlistsarmkernelmsg882962。html
本文作者:宋宝华
本文首发在内核工匠微信公众号,欢迎关注公众号获取最新Linux技术分享。
火箭拒绝7队报价!戈登执意加盟76人北京时间7月8日,随着NBA选秀大会的结束,越来越多的球队开始在自由市场大展拳脚。继爵士交易戈贝尔,马刺送走默里之后,又有多笔重磅签约先后完成,截止到目前,正在酝酿中的交易中,戈登
即将进入七下八上关键期防汛工作面临南北双重压力中国青年报客户端北京7月8日电(中青报中青网记者高蕾)当前,全国防汛工作即将进入七下八上关键期,防汛工作面临南北双重压力,防汛形势不容乐观。7月8日,在国新办举行的国务院政策例行吹
参军政策迎来新变化,与政审有关,预计2023年开始执行如果仔细观察,你会发现,当发生重大灾害时,士兵们总是冲到前线,有时会用自己的身体挡住洪水,他们可以说是最美丽的军人,帮助人民解决了许多困难。许多学生也有参军的梦想后,看到它,他们希
安倍遇刺,女保镖失招今天上午10点多,在日本奈良为参议院竞选造势的日本前首相安倍晋三,刚讲了不到三分钟,随着一团轻雾腾起,安倍中枪倒地,至今生死未卜。作为在世的日本前首相,安倍晋三自然少不了有安保待遇
安倍的保安人员众多,可为什么还发生了意外说到保护重要人物,我们首先想到的是保镖!作为一位国家元首被刺杀这不是一件新鲜事,历史上发生过很多类似的事!在这个时代发生了,因为这个时代不同于历史时期,让人觉得很新鲜!首先我们要看
周杰伦新歌首发争桌位,暴露平台流量焦虑等快餐后遗症7月8日零点十分,周杰伦新专辑最伟大的作品正式开启预售,截至800,定价30元的新专辑售出1073562张。专辑列表显示,除了此前已公布MV的最伟大的作品之外,列表内还有五首老歌,
聚焦钢铁等行业安徽省十四五节能减排实施方案印发7月6日,安徽省人民政府网站发布了安徽省人民政府关于印发安徽省十四五节能减排实施方案的通知(以下简称通知)。通知指出,到2025年,安徽省单位生产总值能耗比2020年下降14,力争
大反攻!大涨超55,疯狂吸金超360亿中国基金报记者曹雯璟近两个多月以来,港股互联网板块走出一轮强劲的反弹行情。中证港股通互联网指数自3月15日低点以来反弹幅度已超44,一批中概互联基金迅速回血,最高反弹超55。同时资
这6种美食明明是中国造,却取了个洋名,日本国民都爱吃这6种美食明明是中国制造,却因为取了个洋气名字,被误会是外国美食。外国的很多文化,和我国都是有很大区别的。现在很多外来的物品,它们的名字大部分都是通过音译之后转化为汉字的名字,所以
夏粮收购全面展开进度快于往年央视网消息(新闻联播)眼下,我国夏粮收购已全面展开。国家粮食和物资储备局最新发布的数据显示,目前主产区收购进度明显快于往年。今年夏粮生产获得丰收,夏粮收购是全年首季粮食收购。5月下
15个副省级城市之一,湖北省的武汉市,为何有13个区县?在之前的文章中,作者和大家聊了一系列关于我国各省区划变迁的故事,例如襄阳市荆州市宜昌市等等。今天,我们聊聊湖北省的省会武汉市。武汉市的综合实力很强,在中部地区,拥有很强的优势。目前