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

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

更好的探查者的动机

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

虽然内置的Go Profiler与其他语言没有探查器更好,但对于其他语言而言,Go的事实上的CPU分析器对基于Linux的系统(并且可能在其他象魔上有很大的限制,并且缺少许多123.4.]完全了解CPU瓶颈所需的细节。

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

  1. 获得更准确和精确的GO程序配置文件的能力
  2. 能够监控各种CPU事件,如缓存未命中,套接字间(NUMA)流量,CPU分支错误预测,命名
  3. 能够监控GO程序的采样频率非常高的 - 高达10秒的微秒

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

背景

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

  1. 通过公开的HTTP端口获取CPU配置文件
    首先,通过在Go程序中包括以下代码片段,公开指定端口上的分析端点:
    go func(){log.println(http.listenandserve(“localhost:6060”,nil))}()

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

    $ curl -o timer.prof server200:6060 / debug / pprof / profile?秒= 5

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

  2. 从Go基准测试中获取CPU配置文件
    GO测试框架允许基准测试程序,然后通过传递额外的方式来分析基准-cpuprofile.标志收集CPU配置文件。
    $ go test -bench benchmarkxyz -cpuprofile cpuprof.out
  3. 从代码仪器获取CPU配置文件
    一个可以插入开始/停止分析API围绕兴趣的代码区域并提供IO.Writer.要将生成的配置文件刷新到文件,如下所示:
    F,ERR:= OS.CREATE(“CPUPROF.PROF”)defer f.close()pprof.startcpuprofile(f)mycodetoprofile()pprof.stopcpuprofile()

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

准确度和精度

准确性和精度是良好测量工具的基本性质。

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

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

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

不准确及其原因

概况的不准确性来自2个来源:采样频率抽样偏见

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

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

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

不精确及其原因

两个主要原因导致大多数测量不精确:样本大小测量滑动

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

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

示范

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

表1:使用操作系统定时器的PPROF配置文件的不准确性和不准确性的展示。

在相同的配置下进行测量3次跑1跑2., 和跑3.表1中的标题列预期的 (%)列显示每个例程中的预期相对时间。这平(MS)列显示了10个例程中的每一个的绝对毫秒测量,以及平坦的 (%)列在每次运行中的总执行时间方面显示例程中的相对时间。这排序列为每个运行的每个函数的执行时间的降序排列排列。

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

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

其他详细信息出现在Golang提案中这里

超越代码热点

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

解决硬件性能计数器的解决方案

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

  1. 提高准确性和精确度:现代商品CPU配备了硬件性能监控单位(PMU)。可以将硬件性能计数器配置为非常精细的测量粒度,这提高了分析精度。此外,在中断时,PMU提供功能的提升功能,该功能记录CPU状态,这允许重建程序的精确状态,从而提高精度。最后,可以将来自硬件PMU的中断配置为被传递到有问题的特定线程,这避免了归因不正确的问题,导致更高的分析精度。
  2. 洞察多种CPU事件,以便更好地诊断性能问题:诊断复杂性能问题时,单独的时间不够。例如,如果大量时间归因于数据结构步行,可能是显而易见的原因。硬件性能监控显示CPU和内存子系统的大量内部功能,允许诊断性能问题的根本原因。例如,如果在再次访问了预先访问的数据之前,将从CPU缓存中访问很多新数据,则如果要配置CPU高速缓存未命中,则会显示出可见。同样,如果2个独立的线程访问2个相邻的数据项,但它们碰巧驻留在同一CPU缓存行上,它会导致2 CPU的私人高速缓存之间的共享高速缓存行ping ping,通常称为虚假共享,可以通过在现代英特尔CPU中称为HITM的计数器诊断。
  3. 高频测量:由于PMU可以配置任意低采样阈值,因此可以以极高的频率(10s或100s的10秒的顺序)监视事件,这对于延迟敏感的服务变得必要,其中单个请求持续到仅几十毫秒。

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

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

表1:PPROF ++中的预设性能监测事件集。

图1:PPROF ++生成的样本呼叫图对于上一级缓存MPMU事件。

图2:PPROF ++生成的样本火焰图对于上一级缓存MIM事件的PPROF ++。

输出PPROF ++是相同的,熟悉的PPROF协议缓冲区文件文件,可以用PPROF工具作为呼叫图(图1)或火焰图(图2),并且还被馈送到其他下游配置文件流程。如果使用PMU拍摄了配置文件循环事件,PPROF呼叫图和FLAME-GRAPH将针对哪些CPU循环的代码区域(上下文)帐户定位和量化。如果使用PMU拍摄了配置文件cachemisses.事件,PPROF呼叫图和Flame-Graph将针对哪些代码区域(上下文)帐户进行查找,以获取哪些CPU上次级别缓存未命中的代码区域(上下文)。

入门PPROF ++

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

描述的简介收集的3种技术背景使用时部分仍然很大程度上相同PPROF ++

1.通过暴露的端口获取CPU配置文件

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

    1. Event = 
    2. 句点= 

以下是一些使用示例:

    1. 通过在500万CPU周期中采样一次调用呼叫堆栈,使用CPU周期收集配置文件,并收集配置文件25秒。
      $ curl -o cycles.prof  / debug / pprof / profile?事件= cycles \&subce = 5000000 \ x x = 25

      事件=循环一世向分析器绑定到样本CPU周期。默认值是PPROF中的“计时器”。期间= 5000000.指示Profiler每5M CPU周期拍摄一个样本。这将导致2.5GHz CPU上每秒约500个样本。秒= 25.指示探查器测量申请25秒。默认值为30秒。

    2. 通过在100万退休指令中对呼叫堆叠进行采样一次,使用CPU退休指令事件收集配置文件,并收集30秒(默认)的配置文件。
      $ curl -o ins.prof  / debug / pprof / profile?事件=指令\&subce = 1000000
    3. 通过在10k缓存未命中采样调用堆栈一次,使用上级缓存未命中收集配置文件并收集30秒的配置文件。
      $ curl -o cachemiss.prof  / debug / pprof / profile?event = cachemisses \&subce = 10000
    4. 收集配置文件以检测由于2个不同NUMA套接字的2个核心之间的真实或假共享而导致缓存行争用。这很容易用mem_load_l3_miss_retive.remote_hitm.有的事件mask0x4.事件代码0xD3.在天窗建筑上。因此我们套装Event = R04D3.。让我们在10K这样的事件中呼叫堆栈样本。
      $ curl -o remote_hitm.prof  / debug / pprof / profile?事件= r04d3 \&subce = 10000

2.从GO基准测试中获取CPU配置文件
我们向命令行介绍2个新参数:

    1. CPUEVENT = 
    2. cpuperiod = 

以下是一些使用示例:

  1. 从CPU收集配置文件循环柜台BenchmarkXYZ.通过每一个抽样百万循环并写下概要文件cycles.prof.文件。
    $ go test -bench benchmarkxyz -cpuprofile cycles.prof -cpuprofileevent cycles -cpuprofileperiod 1000000
  2. 收集档案错误的分支在里面BenchmarkXYZ.通过每一个抽样10000错误的分支并写下档案mispredbranch.prof.prof.
    $ go test -bench benchcharkxyz -cpuprofile mispredbranch.prof -cpuprofileevent branchmisses -cpuprofileperiod 100000
  3. 确定代码在CPU的第一级指令缓存中频繁未命中的位置。这可以通过活动来对英特尔天窗机械进行剖析frontend_retired.l1i_miss.有这件事面具0x1.活动代码0xC6.。让我们举行一次10000错过。
    $ go test -bench benchcharkxyz -cpuprofile insl1miss.prof -cpuprofileevent r01c6 -cpuprofileperiod 10000

3.从代码仪器获取CPU配置文件

PPROF ++将新API引入运行时/ PPROF包。pprof.startcpuprofilewithconfig(opt profilingoption,whitepts
... ProfilingOption)错误在哪里ProfilingOption.以下是以下之一:

funosimer.(w io.writer)ProfilingOption.funcpucycles(W IO.WRITER,期间UINT64)ProfilingOption.funCPUInstructions.(W IO.WRITER,期间UINT64)ProfilingOption.funcpucacherence(W IO.WRITER,期间UINT64)ProfilingOption.funcpucachemisses.(W IO.WRITER,期间UINT64)ProfilingOption.funcpubRanchInstructions.(W IO.WRITER,期间UINT64)ProfilingOption.funcpubranchmisses.(W IO.WRITER,期间UINT64)ProfilingOption.funCaurawevent.(W IO.WRITER,期间UINT64,HEX UINT64)ProfilingOption.

我们现在可以使用startcpuprofilewithconfig / stopcpuprofile.剖析利益代码区域周围的API。

  1. 配置CPU循环事件采样一次的代码区域,在100万CPU周期中。
    f,_:= os.create(“cpuprof.prof”
    defer f.close()
    pprof.startcpuprofilewithconfig(cpucycles(f,1000000)))
    mycodetoprofile()
    pprof.stopcpuprofile()
  2. 我们允许Power用户在一次运行中同时收集多个事件。该工具在环境变量下受到保护go_pprof_enable_multiple_cpu_profiles = <真的|错误的>。每次活动都需要自己IO.Writer.。下面的例子显示收集4个同时配置文件:CPU周期(一个10米),退休指令(一个1M),最后一级缓存未命中(一个10K中的一个),并在第二级TLB中错过的退休加载指令(一个在1K)中,可提供活动mem_inst_retive.stlb_miss_load.面具= 0x11事件代码= 0xD0
    cyc,_:= os.create(“cycprof.prof”
    defer cyc.close()
    INS,_:= OS.CREATE(“insprof.prof”
    推迟ins.close()
    缓存,_:= os.create(“cacheprof.prof”
    defer cache.close()
    tlb,_:= os.create(“tlb.prof”
    defer brmiss.close()
    pprof.startcpuprofilewithconfig(c c cyc,1000000),CPUInstrics(INS,1000000),cpucachemisses(缓存,10000),caurawevent(“r11d0”1000)))
    mycodetoprofile()
    pprof.stopcpuprofile()


视频演示

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

结论和可用性

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

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

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

  1. go1.15.8.linux-amd64.tar.gz.
  2. go1.16.linux-amd64.tar.gz.

几个示例程序使用PPROF ++是在场的SRC / NET / HTTP / PPROF /示例/SRC /运行时/ PPROF /示例/

承认

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

注释

没有帖子展示