修志龙_ZenonXiu · 2023年04月23日 · 上海市浦东新区

SPE profiling及其使用

今日是周末,上海的天气陡然降至冬日的感觉,恰好小朋友学校春游了。正好宅在家里一整天,码了此文。

SPE简介

Arm Statical Profiling Extension (SPE)是从armv8.2开始引入的,它提供了硬件上的统计采样的支持。
我们已经有了PMU为什么还要SPE呢?

Untitled Diagram.jpg

PMU可以通过累计计数event(如cache miss)的方式获取代码执行过程中总的event数量。从而从总体宏观上得到应用/系统运行过程的发生的event(如cache miss)的信息。利用perf tool的’perf stat’可以获取这些信息。

perf stat -e l1d_cache_refill,l1d_cache  ./false_sharing -n 1000000000 -t 0 -c 5 6

Performance counter stats for './false_sharing -n 1000000000 -t 0 -c 5 6':
 
        85,646,174      l1d_cache_refill
     4,012,218,750      l1d_cache

其工作方式为:

未命名绘图-第 7 页1.jpg

PMU也可以实现当设置观测的event如cache miss到达设置的数量(count number)后产生PMU中断,通过中断异常的ELR/LR获取这是被中断指令的地址(即被采样点),在PMU中断处理中,产生perf event record放到perf ring buffer中。利用这样的周期性采样获取的信息,可以大致从统计学意义上分析性能,如代码热点和那些代码导致了那些event(如cache miss/TLB miss/branch mispredication)。利用perf tool的’perf record‘可以获取这些信息,利用’perf report’可以分析这些信息。

未命名绘图-第 7 页2.jpg

一个例子:
通过perf record命令来收集perf数据:

perf record -c 5000  -e l1d_cache_refill,l1d_cache  ./false_sharing -n 1000000000 -t 0 -c 5 6

通过perf report来分析收集的perf数据:

#
# Samples: 19K of event 'l1d_cache_refill'
# Event count (approx.): 97580000
#
# Overhead  Command        Shared Object      Symbol
# ........  .............  .................  .............................
#
    50.01%   T0            false_sharing      [.] 0x0000000000000eb4
    49.78%   T1            false_sharing      [.] 0x0000000000000f1c
     0.13%   T1            false_sharing      [.] 0x0000000000000f18
     0.05%   T1            false_sharing      [.] 0x0000000000000f28
     0.01%   T1            [kernel.kallsyms]  [k] __do_softirq
     0.01%   T0            false_sharing      [.] 0x0000000000000eb8
     0.01%   T0            false_sharing      [.] 0x0000000000000ec0
     0.01%  false_sharing  [kernel.kallsyms]  [k] PageHuge
     0.01%  false_sharing  [kernel.kallsyms]  [k] armv8pmu_set_event_filter
     0.01%  false_sharing  [kernel.kallsyms]  [k] perf_iterate_ctx
 
 
# Samples: 837K of event 'l1d_cache'
# Event count (approx.): 4186685000
#
# Overhead  Command        Shared Object          Symbol
# ........  .............  .....................  .......................................
#
    50.24%   T1            false_sharing          [.] 0x0000000000000f1c
    49.71%   T0            false_sharing          [.] 0x0000000000000eb4
     0.01%   T1            false_sharing          [.] 0x0000000000000f18
     0.01%   T0            false_sharing          [.] 0x0000000000000eb0
     0.00%   T1            false_sharing          [.] 0x0000000000000f28

PMU局限性

  1. Perf record采样模式需要利用PMU中断,这可能导致采样位置票移的问题(获取到的被采样指令地址可能在真实触发event count减到0的时刻附近)。因为它利用的是中断服务程序收集采样触发位置。因为现代superscaler CPU从中断源产生中断到它被pipeline检测到可能需要一些cycles, 汇报到中断异常时ELR/lr的地址可能是同时issue指令的某一条指令地址。虽然,从函数级来分析,这些偏移可能没太多影响。但是,如果要将event归结于汇编指令级的某一条代码,可能会出现一些不容易理解的地方。
    例如上面的perf record/report例子:
    实际上false sharing的主要代码(反汇编)为:

    
    f18:   f94002c0        ldr     x0, [x22]
    f1c:   91000421        add     x1, x1, #0x1
    f20:   91000400        add     x0, x0, #0x1
    f24:   f90002c0        str     x0, [x22]
    f28:   eb02003f        cmp     x1, x2
    f2c:   54ffff61        b.ne    f18 <false_sharing_test+0x238>  // b.any
     
    eb0:   f94002a0        ldr     x0, [x21]
    eb4:   91000421        add     x1, x1, #0x1
    eb8:   91000400        add     x0, x0, #0x1
    ebc:   f90002a0        str     x0, [x21]
    ec0:   eb02003f        cmp     x1, x2
    ec4:   54ffff61        b.ne    eb0 <false_sharing_test+0x1d0>  // b.any
    

    但我们看到上面perf annotate,sample到的PC是:
    image.png

eb4这个地址对应的指令是add x1, x1, #0x1,他是eb0: f94002a0 ldr x0, [x21]下面的一条指令。后面我们可以看到利用SPE的profiling不会出现这样的问题,它是:

annotate_2.JPG

  1. 如果event count已经减计数到0,但是这个时候中断被mask了,这可能就会导致一些盲点/错误的热点。因为PMU中断无法被立刻响应,需要延迟到中断被软件enable才能响应,而这时汇报的ELR/lr已经不是真实触发event count减到0的时刻的代码地址:
    AArch64 Linux的perf子系统依赖于PMU overflow中断。通过编程cycle counter在一定时间间隔overflow, 每个overflow中断都会被传送给CPU来进行代码路径的分析。Perf处理这些中断并采样这些运行进程的PC (program counter). 当仅有单个中断屏蔽可以给程序员控制时,任何在禁止中断状态下执行的critical section都无法被采样分析。用户通常抱怨的问题是他们的系统分析结果显示很多的是CPU时间都花在了local_irq_enable()上。这显然是不对的,因为这个函数是通过单条操作PSTATE.I 标志指令来实现的。这个误导信息是由PMU中断总是在IRQ重新被使能时传送给CPU的,perf总是采样到这个函数的PC。
    Arm 2021构架扩展会引入硬件NMI的支持来解决这个问题。可以参考深入理解 Arm A-profile的non-maskable interrupt -NMI https://aijishu.com/a/1060000...
  2. PMU虽然能提供一段代码执行过程中的event信息,但是它不能精确地汇报某一条指令在执行过程的信息,如这条指令执行过程:

    * 有没有导致cache miss,
    * 指令执行的latency,
    * 分支预测有没有预测成功,跳转是taken还是没有taken,预测的地址是什么
    * load/store指令访问的内存虚拟地址和物理地址是什么… ,
    * load/store指令的数据源来在那一级cache/同一cluster的CPU/另外一个Cluster的CPU/另一个socket的CPU
    

这些信息在性能分析/debug的时候可能会有帮助。
一个分析branch predict miss的例子:

 for(i=0;i<TEST_IRT;i++)
   {
    if (__builtin_expect(test_data[i]==1,0))
        val=val*(i+2);
    else
        val=val*(i+3);     
   }

test_data里面是一些随机的0,1。接下来通过perf record -e branch-misses 采样,然后通过perf annotate来分析,得到的结果是:
image.png
从这个branch miss采样点的比例,比较难看出问题,也不知道实际上对应那条跳转指令。

  1. PMU的方式需要产生中断->进入Linux kernel中断处理过程->调用PMU driver->perf driver将record通过软件写入buffer。这些过程是比较耗时耗CPU执行cycle的。为了避免对正常应用/系统的性能影响,这种方式不能将采样频率设置太高。

Arm SPE

Arm SPE可以通过硬件采集CPU pipeline执行过程中的发生的event, 然后根据软件设置的count number周期性地(当采集的event数量达到count number,即count down到0时)采样这时这条指令的执行信息,并由硬件产生SPE的record packet, 并由硬件将packet写入外部内存。你可以简单理解为SPE将之前PMU软件需要做的事情硬件化实现了(SPE能记录的event种类一般会少于PMU event,但也会有一些PMU没有的信息)。
由上面介绍的SPE工作方式可知,SPE采样的指令地址信息是非常精确的,不会漂移。而且采样记录的开销较小,因而采样率可以设置的很高(当然也是有限制的,不会到每条指令都被采样的精度)。

未命名绘图-第 6 页1.jpg

构架上,SPE的采样可以在指令级的,也可是micro operation (uop,一条指令可能在pipeline 里拆分为多个uop,多条指令也可以在pipeline中fuse成一个uop)级别的。Arm Neoverse N1 CPU SPE采样是uop级别的。为了表示简单,下面内容基本不区分指令级和uop级。

SPE还可以提供被采样指令的额外信息,如:

  • 指令本身的地址
  • 访问数据的虚拟和物理地址
  • 指令Issue latency和总latency
  • 事件(如cache miss)
  • 时间戳(timestamp)
  • 这些信息让开发人员获得非常详细的代码执行信息。

SPE工作过程

利用SPE可以分成4个阶段:
未命名绘图-第 1 页.jpg

  1. 以一个可编程的采样间隔从采样总体中选择一个操作(可以限定为某个EL)。这个过程可以加入一些随机,伪随机或扰动到采样间隔中。
  2. 硬件跟踪这个操作在pipeline执行信息,包括PC,事件,时序,数据地址。这既是profiling operation。
  3. 在硬件创建一个采样记录之前,可以设置过滤由step 2产生的profiling operation, 可设置的过滤条件可以是:

    a.    Operation的类型
    b.    事件
    c.    Latency
  4. 如果这个采样没有被step3过滤掉,硬件会创建一个采样记录,它包含step 2的信息。然后这个采样记录会生成一个标准的数据record,并被硬件写入外部内存buffer。

SPE将采样记录序列写入内存,每个采样记录包含一个packet序列,每个包包含:

  • 1或2个字节的header
  • 0,1, 2, 4或8个字节的payload

777.JPG

下面是一个原始数据记录的例子:

未命名绘图-第 2 页.jpg

如果要获取包含PID的context packet,我们需要在kernel配置中使能CONFIG_PID_IN_CONTEXTIDR

SPE packet

packet_type.jpg
Note:

  • 记录PC的address packet包含采样的PC virtual address, EL level和安全状态。
  • Branch操作可能会产生一个记录Branch target address的address packet
  • Data source Packet是CPU实现相关的(Implementation Dependent)
Latency信息

SPE可以记录以下被采样操作的latency信息:

  • Issue latency:此操作从被dispatch到被issue出去执行之间的cycle数。它计数操作在issue阶段的延迟(可能因为后端structure hazard,等待input operand ready等原因)
  • Total latency: 此操作被dispatch到被执行完成之间的cycle数。
  • Translation latency: 此操作从虚拟地址送到MMU到MMU地址翻译完成之间的cycle数。一般用于load/store操作访问的内存地址的MMU翻译过程。
    未命名绘图-第 3 页.jpg
Data Source信息

Data Source信息用于记录一个load操作访问的数据的来源。利用此信息可以分析数据访问来自不同的数据源的延迟,还可以分析实现cache一致性时CPU cache之间snoop的情况从而进一步分析false sharing,或优化数据访问避免相距较远的CPU之间的snoop导致的大延迟。
Data Source的解释和具体的CPU/SoC设计相关,以N1 CPU为例:
888.JPG

未命名绘图-第 4 页.jpg

Event Packet

SPE构架支持的event分为:

  • 构架要求支持的event
  • CPU实现相关的event

以load/store操作和branch操作可以支持的event为例:
999.JPG

Load/store操作:
3.JPG

aaa.JPG

Branch操作:
4.JPG
bbb.JPG

Linux上如何使用SPE

image.png

使能SPE

  1. 使能CONFIG_ARM_SPE_PMU kernel选项
  2. 禁止kpti, 通过加上”kpti=off”选项
    查看 /sys/bus/event_source/devices/arm_spe_0是否存在,说明spe_pmu driver工作正常。然后通过”perf list | grep arm_spe”查看一下perf tools是否支持spe.

通过perf利用SPE

可以通过以下步骤利用SPE profiling:

  1. 获取包含spe的perf data

    perf record -e arm_spe_0/branch_filter=1,load_filter=1,store_filter=1,ts_enable=1,pct_enable=1,pa_enable=1,min_latency=10,jitter=1/ --  ./user_app

可以使用的option包括
ccc.JPG
需要注意的是:设置这些filter不影响上面介绍SPE ‘Sample Population’和’ Sample is taken’过程,只影响最终写入到SPE buffer的record. 例如对于同一个应用,假设应用执行的代码和数据访问不会改变,通过

perf record -e arm_spe_0/load_filter=1,store_filter=1/ -- ./user_app

未命名绘图-第 6 页1.jpg

perf record -e arm_spe_0/load_filter=1/ -- ./user_app

未命名绘图-第 6 页2.jpg

得到的SPE record中的load操作记录是一样。

还可以通过加上—all-kernel或是—all-user设置特定的EL。通过-c xxx选项指定采样间隔(sample interval).

  1. 分析spe perf data

    Perf report -D -i ./perf.data  >  spe_report.txt

    上面命令导出解密过的spe packet的文本。
    捕获55.JPG

Perf report –stdio -i ./perf.data > spe_report.txt

上面命令导出具有更多分析信息的report文本。

捕获66.JPG

Perf mem

‘perf mem’用于获取更详细内存访问信息,在arm上是基于SPE实现的。
Arm上没有和x86一样的直接mem-loads和mem-stores event,但SPE通过适当设置可以得到类似的信息:
捕获444.JPG

perf mem report

ddd.JPG

Perf c2c工具

Perf c2c工具可以用来检测cache line的false sharing的场景。在arm上也可以利用SPE来获取所需要的信息。
当多个Core同时读写同一个内存地址时,需要通过Cache Coherence硬件机制完成一致性,会产生额外的性能开销。这种情况是真正的“共享”。
当多个Core同时读写多个不同的地址,但这些地址由于相邻会被映射到同一个Cache Line时,虽然从程序逻辑上不存在内存地址“共享”,但因为Cache实现机制的问题存在一个隐式的“共享”,我们称之为False Sharing。
cpu-cache-false-sharing-8.gif
Linux kernel v6.0开始支持arm上的perf c2c. arm上不能使用x86的HITM event,取而代之的是SPE记录中的data source, data address和PC。
Arm上使用’SNOOP_PEER’来指示发生在不同CPU间的snoop。我们需要检查’peer snoop’指标来确定false sharing的问题。4828ADC2A7559A024853CF0290B1C5C5.png

AC5198C60498A89402DAF6FC5A97578E.png

Linaro上有一篇blog值得一看:Using the Arm Statistical Profiling Extension to detect false cache-line sharing https://www.linaro.org/blog/u...

Leo Yan也在Linaro会议上分享了LVC21-302: Integration Arm SPE in Perf for Memory Profiling: https://resources.linaro.org/...

Arm streamline对SPE的支持

Arm streamline工具已经支持SPE,可以利用SPE数据进行系统的profiling。
2781.spe-nice.png-1265x0.png
functions-sorted.png-1265x0.png
已经有一篇blog详细介绍了streamline如何使用SPE进行性能分析,这里就不赘述。
https://community.arm.com/arm... 

SPE parser

虽然perf report提供了比较好已经分析好的信息。但有的时候,性能分析人员可能需要自己根据分析需求把原始的SPE record进行处理。
Arm也提供了一个python程序SPE parser,它可以将perf.data转换为cvs或parquet格式
https://gitlab.arm.com/teleme...

5B1BAA5EBF932AD98EC106293E3DF029.png

PMU vs SPE profiling

那么有了SPE profiling是不是可以完全替代PMU profiling呢?
其实不是,PMU和SPE各有所偏重。

  1. PMU有SPE不能提供的全局event计数,比如对整个应用过程的cache miss计数
  2. PMU的event采集方式和SPE不一样。PMU采集一段时间/代码内指令发生的某些event的累计计数。而SPE是达到设定数量(sample interval)操作(load/store+branch)数时采样一个操作发生的event(如这个操作导致的cache miss),SPE更偏向于收集某单一操作的信息。
  3. PMU有SPE没有的event计数:例如strex pass/fail,执行的指令数量/类型等
  4. SPE能提供PMU没有的单条指令执行信息,PMU更多是许多条指令的累计信息。

可以做一个比喻:一家工厂产线上有很多工人,为了观测效率,可以有不同的方式:
1. 每星期获得工人们总的生产产品的数量和总工时。公司老板可能只需要关心一年的产品数量。
2. 当产品每增加到一定数量之后,开始抽查其中一个工人一天的工作情况,什么时候上班,什么时候下班,有没有偷懒。利用这些信息获知工人们的工作效率。但有可能有些工人有概率不会被抽查到。

方式1类似于PMU,方式2类似于SPE。方式1可以得到更宏观一些的信息,而SPE可以获取更微观一些的信息。
因此性能分析时应该采用PMU和SPE结合的方式。有时PMU和SPE可能会给出不一样甚至有些相悖的信息也可以理解。

让我们在利用false sharing这个例子,但我们在perf record的时候同时使用PMU和SPE:

perf record -e l1d_cache_refill,l1d_cache,arm_spe_0/ts_enable=1,load_filter=1,branch_filter=1,store_filter=1,min_latency=1,jitter=1/ -- ./false_sharing -n 100000000 -t 0 -c 5 6

在通过perf report来看看:

通过PMU的分析为:

# Total Lost Samples: 0
#
# Samples: 7K of event 'l1d_cache_refill'
# Event count (approx.): 9156492
#
# Children      Self  Command        Shared Object      Symbol
# ........  ........  .............  .................  ..............................
#
    49.83%    49.83%   T1            false_sharing      [.] 0x0000000000000f1c
    49.81%    49.81%   T0            false_sharing      [.] 0x0000000000000eb4
     0.20%     0.20%  false_sharing  [kernel.kallsyms]  [k] perf_event_alloc
     0.08%     0.08%  false_sharing  [kernel.kallsyms]  [k] kfree
     0.02%     0.02%   T1            [kernel.kallsyms]  [k] arch_local_irq_enable
     0.01%     0.01%   T0            [kernel.kallsyms]  [k] arch_local_irq_enable
     0.01%     0.01%   T0            false_sharing      [.] 0x0000000000000eb0
     0.00%     0.00%  false_sharing  [kernel.kallsyms]  [k] handle_mm_fault



# Samples: 7K of event 'l1d_cache'
# Event count (approx.): 404996532
#
# Children      Self  Command        Shared Object          Symbol
# ........  ........  .............  .....................  .............................
#
    49.94%    49.94%   T0            false_sharing          [.] 0x0000000000000eb4
    49.93%    49.93%   T1            false_sharing          [.] 0x0000000000000f1c
     0.03%     0.03%  false_sharing  [kernel.kallsyms]      [k] vm_unmapped_area
     0.03%     0.03%  false_sharing  [kernel.kallsyms]      [k] next_uptodate_page
     0.03%     0.03%  false_sharing  [kernel.kallsyms]      [k] unmap_page_range
     0.01%     0.01%   T0            false_sharing          [.] 0x0000000000000eb0
     0.01%     0.01%  false_sharing  ld-linux-aarch64.so.1  [.] 0x0000000000009dec
     0.01%     0.01%   T1            false_sharing          [.] 0x0000000000000f18
     0.01%     0.01%   T1            false_sharing          [.] 0x0000000000000f24
     0.00%     0.00%  false_sharing  [kernel.kallsyms]      [k] inode_permission
     0.00%     0.00%   T0            [kernel.kallsyms]      [k] __handle_mm_fault

而通过SPE得到的分析为:

# Samples: 2K of event 'l1d-miss'
# Event count (approx.): 2666
#
# Children      Self  Command        Shared Object          Symbol
# ........  ........  .............  .....................  .......................................
#
    53.30%    53.30%  false_sharing  false_sharing          [.] 0x0000000000000f18
     46.49%     46.49%  false_sharing  false_sharing          [.] 0x0000000000000eb0
     0.60%     0.60%  false_sharing  [kernel.kallsyms]      [k] perf_iterate_ctx
     0.53%     0.53%  false_sharing  [kernel.kallsyms]      [k] perf_event_task_tick
     0.19%     0.19%  false_sharing  [kernel.kallsyms]      [k] perf_event_wakeup
     0.11%     0.11%  false_sharing  [kernel.kallsyms]      [k] perf_event_alloc
     0.08%     0.08%  false_sharing  [kernel.kallsyms]      [k] check_preempt_curr
     0.08%     0.08%  false_sharing  [kernel.kallsyms]      [k] ctx_sched_out
     0.08%     0.08%  false_sharing  [kernel.kallsyms]      [k] event_sched_out


# Samples: 349K of event 'l1d-access'
# Event count (approx.): 349656
#
# Children      Self  Command        Shared Object          Symbol
# ........  ........  .............  .....................  ..........................................
#
    65.61%    65.61%  false_sharing  false_sharing          [.] 0x0000000000000f18
    32.11%    32.11%  false_sharing  false_sharing          [.] 0x0000000000000eb0
     0.26%     0.26%  false_sharing  [kernel.kallsyms]      [k] perf_output_copy
     0.18%     0.18%  false_sharing  [kernel.kallsyms]      [k] sched_clock
     0.15%     0.15%  false_sharing  [kernel.kallsyms]      [k] armv8pmu_handle_irq

PMU和SPE的分析结果有很大的差异:
PMU的分析两个CPU/Thread采样到的L1 cache refill/access是很接近的。而且除了采样对测试变量的LDR指令之外(虽然PC偏移了4 byte,实际应该为0x0f18和0x0eb0,有0.01%采样到PC准确为0x0b0和0x0f18),它还采样到少部分对测试变量的STR指令

 0.01%     0.01%   T1            false_sharing          [.] 0x0000000000000f24

SPE的分析两个CPU/Thread采样到的L1 cache refill/access相差较大,而且只采样到对测试变量的LDR指令。通过SPE parser分析每条SPE record也可以确认的确没采样到对测试变量的STR指令。

为了分析,我在再使用perf stat -e l1d_cache_refill,l1d_cache --cpu 来获取每个CPU的L1 cache refill/access的统计/累计数量:

perf stat --cpu 5 -e l1d_cache_refill,l1d_cache  ./false_sharing -n 100000000 -t 0 -c 5 6

 Performance counter stats for 'CPU(s) 5':

         4,600,775      l1d_cache_refill
       200,844,437      l1d_cache
perf stat --cpu 6 -e l1d_cache_refill,l1d_cache  ./false_sharing -n 100000000 -t 0 -c 5 6
 Performance counter stats for 'CPU(s) 6':

         5,340,258      l1d_cache_refill
       200,885,890      l1d_cache

由此看见,两个CPU/Thread采样到的L1 cache refill/access应该是比较相近的。
可能的解释是:
cache refill可能发生在改这个变量的LDR指令上,也可能在STR指令上。但SPE只采样到了LDR指令,而SPE的分析是只基于这些LDR指令发生的cache refill/access进行统计,可能导致偏差。
但PMU不存在这样的问题,因为即使是绝大多数采样到的也是LDR指令,但是PMU的cache miss/TLB miss的信息已经包含了上次采样到本次采样之间(包括LDR+STR)发生的这些event.

需要注意的是:N1 CPU的某些版本SPE有点小问题:SPE硬件采样到STR指令时,SPE record不会包含STR指令的cache miss/TLB miss event packet.

从这个例子可以看出PMU分析可以给出给宏观的分析结果,然后可以进一步利用SPE的data source这样独用的信息分析出是因为false sharing导致的性能降低。

Arm Topdown 性能分析工具
PMU, AMU的区别

参考
Integration Arm SPE in Perf for Memory Profiling: https://resources.linaro.org/...

推荐阅读
关注数
8627
内容数
50
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息