PPROF ++:具有硬件性能监控的GO Profiler

2021-05-11 23:47:27

Golang是成千上万的超级后端服务的生命线,以数百万CPU核心运行。了解我们的CPU瓶颈是至关重要的,用于减少服务延迟,也是为了使我们的计算舰队有效。优步操作的规模需要深入了解代码和微体系结构的洞察。

虽然内置的Go Profiler与其他语言没有任何其他语言,但Go的事实上的CPU分析器对基于Linux的系统(并且可能在其他oS的其他语言中具有严重限制,并且缺少许多[1,2 ,3,4]完全了解CPU瓶颈所需的详细信息。

通过这些担忧,我们旨在建立一个完全适合我们的需求和优步业务的规模的自定义Go探查器。具体而言,通过将丰富的硬件性能监控功能集成到其中,我们通过将丰富的硬件性能监控功能集成来增强Go的默认PPROF分析器。此进步提供以下主要优惠:

能够监控各种CPU事件,如缓存未命中,套接字间(NUMA)流量,CPU分支错误预测,命名

能够监控GO程序的采样频率非常高的 - 高达10秒的微秒

所有这些功能都具有相同,熟悉的PPROF接口和能够重用先前与PPROF的配置文件(协议缓冲区)文件一起使用的所有下游工具。这意味着我们可以重用调用堆栈归属,呼叫图和火焰图来命名几个。

剖析是戈兰内置的功能之一。 Go Profiler涵盖了CPU时间,内存分配等方面的方面。本文涉及最常见和熟悉的分析形式 - CPU分析。从GO程序获取CPU配置文件有3个众所周知的方法:

通过在Go程序中包含以下代码片段,在公开的HTTP端口缩短CPU配置文件,在指定端口上公开分析端点:

然后,从运行Go程序中获取配置文件的配置文件;例如,以下命令包含5秒的CPU配置文件并将其存储在Timer.prof文件中。

在Uber,我们依靠我们从数千个生产微服务中获取配置文件的能力,我们有一个精心制作的下游工具来处理它们。

2.从Go基准测试Go测试框架获取CPU配置文件允许基准测试程序,然后通过传递额外的-cpuprofile标志来分析基准,以收集CPU配置文件

从代码仪器获取CPU配置文件可以在兴趣区域周围插入开始/停止分析API,并提供IO.WRITER以将结果的配置文件刷新到文件,如下所示:

所有三个接口都生成PPROF协议缓冲区文件,可以使用GO工具PPROF< profile文件&gt查看。命令行或其他下游工具。 GO CPU配置文件包括调用堆栈样本的时间归因。它们可以通过呼叫图和火焰图可视化。

如果它接近地面真相,则据说一个分析数据准确。例如,如果api_a()消耗总执行时间的25%和分析器属性的24%的总执行时间,则测量的精度为96%。

据说分析数据是精确的,如果多次测量的可变性低。相同变量的一组测量的精​​度通常表示为min且来自样本的最小值,或者是样本的标准误差或变异系数。

不幸的是,目前的PPROF CPU配置文件既不会遇到这两个标准。在Go运行时,CPU分析采用操作系统定时器定期(〜100次)中断执行。在每个中断(aka样本)上,它收集同时发生的呼叫堆栈。

曲线的不准确性产生2个来源:采样频率和采样偏置。采样频率:基于采样的分析器的准确性与Nyquist采样定理松散地相关。为了忠实地恢复输入信号,采样频率应大于信号中包含的最高频率的两倍。如果MicroService需要,例如10毫秒,请处理请求,我们应该至少每5毫秒来进行样本,以获得某种意义。但是,操作系统定时器不能低于每10毫秒的一个样本。实际上,对于功能级别的准确配置文件,我们需要在微秒粒度下的样品。

更大数量的样本可以使易于现实更靠近现实的配置,并且可以通过增加测量窗口的长度来获得更多样本。线性缩放测量时间以收集更多样本是不切实际的,如果需要更多的样本以纠正小样本尺寸问题。因此,存在尖端需要在测量窗口内收集更多样本并获得细粒的子毫秒执行样本。

取样偏置:如果样品偏置,则单独的样本大小不提高精度。 OS定时器(ITIMER)Linux中的样本被偏置,因为从一个OS线程中断定时器中断,例如可以通过任意(不与均匀随机)的方式处理T2,说T2。这意味着T2将处理中断和错误地将样本属性归因于其呼叫堆栈,这导致偏置样本。如果与T1相比处理的更多定时器中断,则会发生系统采样偏差,这导致不准确的轮廓。

样本大小:更少数量的样本直接有助于大量标准误差。 OS定时器的低分辨率负责测量窗口中的样本较少,这导致PPROF配置文件的较低精度。相反,样品的更高分辨率将提高精度。

测量SKID:第二测量误差是任何给定测量设备固有的随机误差。配置为在N毫秒后到期的操作系统定时器设备仅保证在N毫秒后的一段时间 - 不准确地在N毫秒内生成中断。这种随机性在测量中引入了大的“滑动”。假设定期定时器设置为每10ms射击。假设它在其到期前剩余1ms,当OS调度程序带有4ms分辨率检查它时。这意味着计时器将在以后将射击4ms,在预定的到期时间之后是3ms。这导致10ms定期定时器的不精确度高达30%。虽然可能不完全消除随机误差,但是卓越的测量装置减少了随机误差的影响。

考虑Go Program Goroutine.go,它具有10个完全相似的Goroutines Main.f1-main.f10,其中每个功能采用完全相同的CPU时间(即,总体执行的10%)。表1总结了使用该程序的默认PPROF CPU配置文件的结果,在带有Linux操作系统的12核Intel Skylake服务器类计算机上运行:

测量值3次在运行1,RUN 2,并在表1中运行3个标题列的相同配置。预期的(%)列显示每个例程中的预期相对时间。扁平(MS)列显示出归因于10个例程中的每一个的绝对毫秒测量,并且扁平(%)列在每个运行中的总执行时间方面显示了例程中的相对时间。等级排列列为每个运行的每个函数的执行时间的降序排列排列。

考虑运行1中的数据。 main.f1-main.f10在归因于它们的时间内具有广泛的差异,而我们预计其中每一个是总时间的10%。归因于归因于归因于最高归属量(Main.f7,带有4210ms,23.31%)和归因最低的例程的时间差异(Main.f9,具有700ms,3.88%)的例程。这证明了基于PPROF定时器的配置文件的差的准确性差(偏差)。

现在专注于3运行运行1,Run2,并一起运行3。归因于任何例程的时间从一个跑到另一个例程都广泛变化。常规的等级顺序从运行运行显着变化。在Run 1中,Main.f7显示为使用级别为1的4210ms,而在运行2中,它显示为仅使用秩序10运行520ms。期望是测量结果与运行相同跑步。主要的3次运行中存在71%的差异系数。所有功能的平均方差系数是28%。这表明PPROF基于定时器的配置文件的不精确。增加此程序的运行时间为10x甚至100倍不正确的此分析错误。

分析器的第二个方面正在使其提供更多细节,而不是仅仅是热点,以帮助开发人员采取纠正措施。所有档案显示热点,但热点是最多只有症状,而且没有完全反映出潜在的问题。例如,如果探查器显示该程序在矩阵矩阵乘法中花费90%的时间,则不判断它是否好坏;矩阵乘法可能高度优化!但是,如果探查器可以精确地从其运行的NUMA系统的远程DRAM获取矩阵矩阵乘法期间访问的50%的数据,它立即突出了程序中的数据局部问题,因此优化。这些细节难以通过基本探查器来实现,默认的Go Profiler包括在内。

为了解决这些问题,我们开发了PPROF ++,它增强了默认的GO CPU分析器来使用硬件性能监控功能。

提高准确性和精确度:现代商品CPU配备了硬件性能监控单位(PMU)。可以将硬件性能计数器配置为非常精细的测量粒度,这提高了分析精度。此外,在中断时,PMU提供功能的提升功能,该功能记录CPU状态,这允许重建程序的精确状态,从而提高精度。最后,可以将来自硬件PMU的中断配置为被传递到有问题的特定线程,这避免了归因不正确的问题,导致更高的分析精度。

探讨多种CPU事件,以便更好地诊断性能问题:在诊断复杂性能问题时,单独的时间不充分。例如,如果大量时间归因于数据结构步行,可能是显而易见的原因。硬件性能监控显示CPU和内存子系统的大量内部功能,允许诊断性能问题的根本原因。例如,如果在再次访问了预先访问的数据之前,将从CPU缓存中访问很多新数据,则如果要配置CPU高速缓存未命中,则会显示出可见。同样,如果2个独立线程访问2个相邻的数据项,但它们碰巧驻留在同一CPU高速缓存行上,它会导致2 CPU的私有高速缓存之间的共享高速缓存行ping ping,通常称为假 - 共享,可以通过现代英特尔CPU中称为HITM的计数器诊断。

高频测量:由于PMU可以配置任意低采样阈值,因此可以以极高的频率(10s或100s的10秒)以极高的频率监测事件,这对于延迟敏感的服务成为个人请求的持续敏感服务所必需的只有数十毫秒。

PMU在现代CPU中是普遍存在的,由Linux等SUSE公开,它们可以编程方式控制,以提供累积阈值事件数量的中断。例如,英特尔为其处理器系列的每个成员发布其性能监视事件,Linux通过Perf_Event子系统公开PMus。 PPROF ++对Go运行时的更改进行了更改,订阅这些硬件事件,并获取定期中断。该机制通常被称为基于硬件的基于事件的采样。活动选择使硬件性能监测丰富和洞察力。与OS定时器分析一样,PPROF ++在每个PMU中断和属性事件上执行呼叫堆栈,以调用堆栈样本。这允许PPROF ++配置文件突出显示哪些函数和源线导致不同的架构瓶颈,例如CPU缓存未命中。

对于初学者来说,识别要令人生畏的监视器的硬件性能事件;因此,我们简化了过程并公开了以下最常见的事件。事件具有预设的采样周期,可以覆盖。

PPROF ++的输出是相同的熟悉的PPROF协议缓冲区文件文件,可以用PPROF工具作为呼叫图(图1)或火焰图(图2),并且还被馈送到其他下游轮廓处理工作流程。如果使用PMU循环事件拍摄了配置文件,则PPROF呼叫图和火焰图将针对哪些CPU周期进行查明和量化哪个代码区域(上下文)占多样的CPU周期;如果使用PMU Cacchemisses事件拍摄了配置文件,则PPROF呼叫图和FLAME-GRAPL将针对哪些代码区域(上下文)帐户占用哪些CPU上次级别缓存未命中等。

为了利用PPROF ++,通过我们的自定义GO编译器重新编译您的GO程序,使用PMU分析设施增强;请参阅下面的可用性部分。

在使用PPROF ++时,在后台部分中描述的配置文件集合的3种技术在很大程度上是相同的:

在PPROF的情况下,包括相同的代码片段以暴露分析端点;应用程序代码不需要更改。开发人员现在可以取消许多品种的简档。我们向/ debug / pprof / profile端点介绍2个新参数

通过在500万CPU周期中采样一次调用呼叫堆栈,使用CPU周期收集配置文件,并收集配置文件25秒。

Event =循环指示对样本CPU周期的分析器。默认值是PPROF中的“计时器”。周期= 5000000表示分析器每5M CPU周期拍摄一个样本。这将导致2.5GHz CPU上每秒约500个样本。秒= 25表示分析器测量申请25秒。默认值为30秒。

湾通过在100万退休指令中对呼叫堆叠进行采样一次,使用CPU退休指令事件收集配置文件,并收集30秒(默认)的配置文件。

C。通过在10k缓存未命中采样调用堆栈一次,使用上级缓存未命中收集配置文件并收集30秒的配置文件。

天。收集配置文件以检测由于2个不同NUMA套接字的2个核心之间的真实或假共享而导致缓存行争用。这很容易使用MEM_LOAD_L3_MISS_RETIFD.REMOTE_HITM事件,该事件在天窗架构上具有蒙版0x4和事件代码0xD3。因此,我们设置了Event = R04D3。让我们在10K这样的事件中呼叫堆栈样本。

2.从GO基准测试CPU配置文件我们向命令行介绍2个新参数:CPUEVENT =<计时器|周期|指示| Cacchemisses | Cachereferences |分支机构| Branchmisses | rhexvalue>和cpuperiod =< int64value> 。以下是一些使用示例:

通过每1000万个周期中的一个在BenchmarkXYZ上采样将CPU周期计数器收集配置文件,并将配置文件写入Cycles.prof文件。

湾通过每10000个错误预定的分支中的一个在每10000个错误的分支中采样,收集BenchmarkXYZ中错误预定的分支的配置文件,并将配置文件写入MispredBranch.prof。

C。确定代码在CPU的第一级指令缓存中频繁未命中的位置。这可以在Intel Skylake机器上与事件FrontEnd_retired.l1i_miss上的Intel Skylake Machine剖析,具有蒙版0x1和事件代码0xC6。让我们在10000次失误中进行一次样本。

PPROF ++将新API引入运行时/ PPROF包。 pprof.startcpuprofilewithconfig(opt profilingoption,morepts ... profilingoption)错误,其中profilingoption可以是以下之一:

func oosimer(w io.writer)profilingoption func cpucycles(w io.writer,uint64)profilingoption func cpuinstructions(w io.writer,uint64)profilingoption func cpucacherences(w io.writer,uint64)profilingoption func cpucachemisses(w io .writer,uint64)profilingoption func cpubranchinalstructions(w io.writer,uint64)profilingoption func cpubranchmisses(w io.writer,uint64)profilingoption func cpurawevent(w io.writer,周期Uint64,hex uint64)profilloption

配置CPU循环事件采样一次的代码区域,在100万CPU周期中。

湾我们允许Power用户在一次运行中同时收集多个事件。该设施在环境变量GO_PPROF_ENABLE_MULTIPLE_CPU_PROFILES =<真实|假> 。每个事件都需要自己的IO.Writer。下面的例子显示收集4个同时配置文件:CPU周期(一个10米),退休指令(一个1M),最后一级缓存未命中(一个10K中的一个),并在第二级TLB中错过的退休加载指令(一个在1K)中,可用事件mem_inst_retive.stlb_miss_load,mask = 0x11,事件代码= 0xD0。

cyc,_:= os.create(" cycprof.prof")defer cyc.close()ins,_:= os.create(" insprof.prof")defer ins.close ()缓存,_:= os.Create(" cacheprof.prof")defer cache.close()tlb,_:= os.create(" tlb.prof")defer brmiss .close()pprof.startcpuprofilewithconfig(C cyccles(cyc,1000000),cpuiningsions(Ins,1000000),cpucachemisses(cache,10000),cpurawevent(" r11d0",1000))mycodetoprofile()pprof.stopcpuprofile( )

下面的视频演示了使用PPROF ++的下载,初始设置和用法来开始。

任何编程语言都需要准确和精确的简档,这些简档可以为程序执行提供更深层次和可操作的见解。优步使用Go for The SicroServices已导致我们将这些功能带入Golang的PPROF Profiler。虽然存在许多具有相似能力的其他第三方配置文件,但在GO的运行时内的PMU分析集成了与Myriad执行环境和下游后处理工具的无缝集成。

我们发布了GitHub上目前实施PPROF ++的原型。我们已经在1.15.8的顶部提供了1.16个释放分支。

PPROF ++目前仅在Linux操作系统上使用。为了快速下载,我们制作了X86_64(AKA AMD64)的Go二进制文件:

鹏飞苏,UC默塞德的助理教授,在2019年夏天在Uber编程系统组中开展了初始原型。