---
title: brbe及其应用-官方技术文章-鲲鹏社区
description: 本文介绍了ARM平台的分支记录机制——brbe，以及brbe的几项重要应用：捕获调用栈、识别热点分支、反馈优化。最后，介绍了libkperf采集brbe的能力。
keywords: 及其应用,官方技术文章,鲲鹏社区,分支记录,介绍,ARM,BRBE,Branch
url: https://www.hikunpeng.com/developer/techArticles/20251205-30?envFlag=1
section: (其他)
---

# brbe及其应用-官方技术文章-鲲鹏社区

URL: https://www.hikunpeng.com/developer/techArticles/20251205-30?envFlag=1
描述: 本文介绍了ARM平台的分支记录机制——brbe，以及brbe的几项重要应用：捕获调用栈、识别热点分支、反馈优化。最后，介绍了libkperf采集brbe的能力。
关键词: 及其应用,官方技术文章,鲲鹏社区,分支记录,介绍,ARM,BRBE,Branch

官方技术文章 [了解详情](https://www.hikunpeng.com/zh/developer/techArticles)

brbe及其应用

brbe及其应用

性能调优同辕开发

发表于 2025/12/05

0

## 1分支记录

### 1.1介绍

在需要程序的指令流时，只需要关注最具代表性的指令——分支跳转指令，因为分支总是基本块中的最后一个指令。现代CPU提供分支记录机制（Branch Recording Mechanisms），使得能够在分支上进行采样，并在每次采样期间，记录先前执行的N个分支。

Inter与ARM平台都支持分支记录机制，它们的基本原则是相同的：硬件记录每个分支的from、to地址以及一些额外数据（例如分支预测位）。

## 2       ARM平台的BRBE（Branch Record Buffer Extension）

### 2.1介绍

BRBE是一个硬件实现的环形缓冲区，保存最近执行的N条分支记录。每条记录内容主要有：分支的源地址、分支的目标地址、分支类型、分支预测结果等。如图所示：

BRBE采集可以限制在一组特定的分支类型上，例如用户可以选择只记录函数调用和返回。用户还可以过滤条件跳转和无条件跳转、间接跳转和调用、系统调用、中断等。在perf中，-j选项可以启用/禁用记录各种分支类型。如下图所示：

例如，一个典型的采样命令：perf record -F 1000 -j u,any -e cycles -p 123456

指定了采样频率、过滤器、采样事件、采样进程。

## 2.2      BRBE的应用

### 2.2.1捕获调用栈

分支记录最广泛的应用之一是捕获调用堆栈。对于这样一个程序，可以通过brbe采样获取调用栈信息：

```
// test.cpp
void func3()
{
    for(int i = 0;i<300000;i++)
    {
 
    }
}
 
void func2()
{
    for(int i = 0;i<200000;i++)
    {
        
    }
    func3();
}
 
void func1()
{
    for(int i = 0;i<100000;i++)
    {
        
    }
    func2();
}
 
int main()
{
    for(int i=0;i<1000;i++)
    {
        func1();
    }
}
```

通过这几步：

```
g++ -g test.cpp -o test
perf record --call-graph dwarf -- ./test
perf report -n –stdio
```

可得到调用栈：

从中可以看出各函数调用路线以及耗时占比。具体来说，func1() → func2() → func3()显然是占用时钟周期最长的调用路线，其主要耗时集中在func3()，占比50.67%，因此整条路线的性能瓶颈即为此处，符合程序本身的结构。对于更复杂的应用，也可以通过类似的方法进行分析。

### 2.2.2识别热点分支

分支记录可以帮助识别哪些代码行占用CPU的比例更高。

例如，对于这样一个sort程序：

```
#include <iostream>
#include <algorithm>
#include <ctime>
 
int main()
{
    // 随机产生整数
    const unsigned arraySize = 32768;
    int data[arraySize];
 
    for (unsigned c = 0; c < arraySize; ++c)
        data[c] = std::rand() % 256;
 
    // 排序
    std::sort(data, data + arraySize);
 
    // 测试部分
    clock_t start = clock();
    long long sum = 0;
 
    for (unsigned i = 0; i < 100000; ++i)
    {
        // 主要计算部分：选一半元素参与计算
        for (unsigned c = 0; c < arraySize; ++c)
        {
            if (data[c] >= 128)
                sum += data[c];
        }
    }
 
    double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
 
    std::cout << elapsedTime << std::endl;
    std::cout << "sum = " << sum << std::endl;
 
    return 0;
}
```

可以通过如下步骤，获取热点分支信息：

```
g++ -g -O0 -o sort sort.cpp
perf record -e cycles -b --call-graph=dwarf  -- ./sort
perf report -n --sort overhead,srcline -F +dso,symbol –stdio
```

结果如图所示：

可以看出性能消耗主要集中在24、26、27行，即程序的计算部分。

### 2.2.3反馈优化

brbe数据经过处理后可用于反馈优化，例如，对于这样一个冒泡排序程序

```
// sort.c
#include <stdlib.h>
#include <sys/time.h>
#define ARRAY_LEN 30000
 
static struct timeval tm1;
 
static inline void start() {
    gettimeofday(&tm1, NULL);
}
 
static inline void stop() {
    struct timeval tm2;
    gettimeofday(&tm2, NULL);
    unsigned long long t = 1000 * (tm2.tv_sec - tm1.tv_sec) +\
                           (tm2.tv_usec - tm1.tv_usec) / 1000;
    printf("%llu ms\n", t);
}
 
void bubble_sort (int *a, int n) {
    int i, t, s = 1;
    while (s) {
        s = 0;
        for (i = 1; i < n; i++) {
            if (a[i] < a[i - 1]) {
                t = a[i];
                a[i] = a[i - 1];
                a[i - 1] = t;
                s = 1;
            }
        }
    }
}
 
void sort_array() {
    printf("Bubble sorting array of %d elements\n", ARRAY_LEN);
    int data[ARRAY_LEN], i;
    for(i=0; i<ARRAY_LEN; ++i){
        data[i] = rand();
    }
    bubble_sort(data, ARRAY_LEN);
}
int main(){
    start();
    sort_array();
    stop();
    return 0;
}
```

可以通过以下步骤反馈优化，减少程序运行时间：

```
(1)   安装autofdo：yum install autofdo.aarch64
(2)   编译原始代码： gcc -g -O3 sort.c -o sort
(3)   执行未优化的二进制文件10次，记录耗时
```

```
(4) 使用perf采集brbe，记录执行时的数据：perf record -e cycles -j any,save_type ./sort
(5) 生成gcov文件：create_gcov --binary=./sort
--profile=perf.data --gcov=sort.gcov gcov_version=2
(6)   使用 AutoFDO 编译优化后的代码：gcc -O3 -fauto-profile=sort.gcov sort.c -o sort_autofdo
(7)   执行优化后的程序10次,对比优化前后程序耗时
```

在本例中，sort.c的耗时显著降低。对于大型应用，也可以在编译时加入反馈优化选项，然后运行benchmark采集brbe，再在二次编译时加入处理后的brbe数据，以此优化应用的性能。

## 3      libkperf采集BRBE

### 3.1介绍

libkperf是一个轻量级linux性能采集库，它能够让开发者以API的方式执行性能采集，包括pmu采样和符号解析。libkperf把采集数据内存化，使开发者能够在内存中直接处理采集数据，避免了读写perf.data带来的开销。

libkperf的开源地址：https://atomgit.com/openeuler/libkperf [了解详情](https://atomgit.com/openeuler/libkperf)

编译生成动态库和C的API：

```
    git clone --recurse-submodules https://atomgit.com/openeuler/libkperf
    cd libkperf
    bash build.sh install_path=/path/to/install
```

libkperf提供了采集brbe的能力，例如：

```
#include <iostream>
#include "symbol.h"
#include "pmu.h"
 
int main()
{
char* evtList[1] = {"cycles"};
int* cpuList = nullptr;
PmuAttr attr = {0};
attr.evtList = evtList;
attr.numEvt = 1; 
attr.cpuList = cpuList;
attr.numCpu = 0;
attr.freq = 1000;
attr.useFreq = 1;
attr.symbolMode = NO_SYMBOL_RESOLVE;
int pidList[1] = {1}; // 该pid值替换成对应需要采集应用的pid
attr.pidList = pidList;
attr.numPid = 1;
attr.branchSampleFilter =
KPERF_SAMPLE_BRANCH_USER | KPERF_SAMPLE_BRANCH_ANY;
int pd = PmuOpen(SAMPLING, &attr);
if (pd == -1) {
    std::cout
<< Perror() << std::endl;
    return;
}
PmuEnable(pd);
sleep(3);
PmuDisable(pd);
PmuData* data = nullptr;
int len = PmuRead(pd, &data);
for (int i = 0; i < len; i++)
{
    PmuData &pmuData = data[i];
    if
(pmuData.ext)
    {
        for (int j = 0; j < pmuData.ext->nr; j++)
        {
            auto *rd = pmuData.ext->branchRecords;
            std::string predStr = "P";
            if (rd[j].misPred == 1) {
                predStr = "M";
            }
            std::cout << std::hex << rd[j].fromAddr << "->"
<< rd[j].toAddr << " " << rd[j].cycles << "
" << predStr << std::endl;
        }
    }
}
PmuDataFree(data);
PmuClose(pd);
}
```

其中branchSampleFilter定义了过滤器，含义与perf record中的-j参数相同。

执行上述代码，输出的结果类似如下：

```
ffff88f6065c->ffff88f60b0c 35 P
ffff88f60aa0->ffff88f60618 1  P
40065c->ffff88f60b00 1 P
400824->400650 1 P
400838->400804 1 P
```

结果展示了分支跳转的起始地址、周期数、预测标志位。

本页内容
