> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 排查性能问题

要解决性能问题，您可以使用基本和高级两种排查方法。

## **基本排查**

基本排查涉及应用层面的基础技术。在出于教育和学术目的使用 Qualcomm 开发套件开发应用时非常有用。基本排查可应用于无需 root 访问权限即可运行 Qualcomm<sup>®</sup> Linux<sup>®</sup> 的设备。

对于更复杂的问题，请参见[高级排查](#advanced-troubleshooting)。

### **分析用户空间和内核跟踪**

Function tracer（ftrace）、Trace Compass 和 LTTng 等工具常用于在 Linux 上分析跟踪数据以排查性能问题。

|   **性能调试工具**  |                                                               **参考**                                                              |
| :-----------: | :-------------------------------------------------------------------------------------------------------------------------------: |
| Trace Compass | [Trace Compass User Guide](https://archive.eclipse.org/tracecompass/doc/stable/org.eclipse.tracecompass.doc.user/User-Guide.html) |
|     LTTng     |                                        [LTTng Documentation](https://lttng.org/docs/v2.13/)                                       |

您可以使用 `-llttng-ust` 和 `-g -finstrument-functions` 编译应用，以显示函数调用栈。

例如，运行以下命令进行编译：

```text theme={null}
aarch64-qcom-linux-g++ <cpp source file> -o <output file> -llttng-ust -g -finstrument-functions
```

通过[编译性能工具](./get-started-with-performance-tuning-and-optimization#compile-performance-tools)启用后，设备上可以使用以下 GCC 和 G++ 编译器：

* `aarch64-qcom-linux-gcc`
* `aarch64-qcom-linux-g++`

### **捕获 LTTng-UST 跟踪**

要使用 LTTng 捕获跟踪，请按以下步骤操作：

1. 要使用 `liblttng-ust-cyg-profile.so` 显示应用的调用栈，请使用以下命令创建名为 my-session 的会话：
   ```text theme={null}
   lttng create my-session --output=/tmp/my-trace
   ```
   跟踪数据位于 `/tmp/my-trace`。
2. 按以下顺序运行命令以捕获跟踪：
   ```text theme={null}
   lttng enable-event -u -a
   ```
   ```text theme={null}
   lttng enable-event -k -a
   ```
   ```text theme={null}
   lttng start
   ```
3. 运行程序时预加载 `liblttng-ust-cyg-profile` 库：
   ```text theme={null}
   LD_PRELOAD=/usr/lib/liblttng-ust-cyg-profile.so ./test_executable
   ```
   ```text theme={null}
   lttng stop
   ```
   ```text theme={null}
   lttng destroy my-session
   ```

### **加载 LTTng 跟踪**

1. 要在 Trace Compass 中加载和可视化 LTTng 跟踪数据，请使用安全复制协议（SCP）或类似工具将跟踪数据从目标设备传输到主机。请确保在命令中指定目标 IP 地址。示例命令如下：
   ```text theme={null}
   scp -r root@10.92.162.185:/home/root/lttng-traces/ <store trace path>
   ```
2. 在主机上使用 Trace Compass 加载 LTTng 内核和 UST 跟踪。在 Trace Compass 工具中，使用 **File** 菜单选项打开跟踪。**注意** 截图仅供参考。截图中显示的目录结构可能因 Trace Compass 工具版本而异。
   <div className="flex flex-col items-center gap-1">
     <img src="https://mintcdn.com/qualcomm-prod/rpHTx_a6zriKQll9/System/Performance/media/k2l-performance/fig-6-1-trace-compass.jpg?fit=max&auto=format&n=rpHTx_a6zriKQll9&q=85&s=b4ed667eec16949f5331301465d869fd" width="299" height="276" data-path="System/Performance/media/k2l-performance/fig-6-1-trace-compass.jpg" />
   </div>
3. 要选择跟踪类型，请右键单击跟踪，然后选择 **Select Trace Type** > **Ftrace Format** > **Raw Textual Ftrace**，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-7.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=0621c1665e2ffd9c2739e2f753e3ea9a" width="901" height="375" data-path="System/Performance/media/k2l-performance/fig-6-7.jpg" />
</div>

4. 在 Trace Compass 中安装 ftrace 分析所需的附加组件。转到 **Menu** > **Tools** > **Add-ons**，并选择 **Trace Compass ftrace**。**注意** 建议更新 Trace Compass 首选项。要打印与原始 ftrace 匹配的时间，请将 **Tracing–Time Format** 更改为 **TTT**（epoch 秒数）。
5. 要在一个视图中显示内核和 UST 跟踪，请创建 Experiments 并添加两个跟踪。

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-2.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=271309aa8e84c52245fb33b558c4b75e" width="515" height="742" data-path="System/Performance/media/k2l-performance/fig-6-2.jpg" />
</div>

6. 选择 **Views** > **LTTng-UST-CallStack** > **Flame Chart and Views** > **Linux Kernel** > **Resources**。Trace Compass 可以显示内核资源和用户空间应用函数调用栈，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-3.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=bbfc593df1cad9933e3de664daea6ab4" width="1290" height="692" data-path="System/Performance/media/k2l-performance/fig-6-3.jpg" />
</div>

7. 按照步骤 6 打开 CPU 频率的跟踪。选择 **Resources** 面板和在指定 CPU 上运行的进程的 **Timeline** 视图。CPU 频率线中有一个频率数字。下图显示 CPU0 到 CPU2 运行在 2 GHz，CPU3 到 CPU5 运行在 2.8 GHz。

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-4.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=d4951a6c4a4b29a7e50854066cca8109" width="1270" height="470" data-path="System/Performance/media/k2l-performance/fig-6-4.jpg" />
</div>

### **监控用户空间应用的 CPU 消耗**

可以使用 top 和 htop 等多种 Linux 实用工具监控 CPU 使用情况。

### **Top**

Top 是一款检查应用 CPU 使用情况并显示总体 CPU 使用率的工具。在八核平台上，任务的 CPU 消耗可以从 0% 到 800%。

要设置终端环境以运行 top，请在设备上运行以下命令：

```text theme={null}
export TERM=xterm
```

```text theme={null}
top
```

下图显示了该命令输出的 CPU 使用情况：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-5.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=66dc0b539911335aa7c6525f4a987eb1" width="687" height="299" data-path="System/Performance/media/k2l-performance/fig-6-5.jpg" />
</div>

### **htop**

htop 显示每个核心的 CPU 使用率以及每个进程的总体 CPU 使用率。要在构建中编译 htop，请参见[编译性能工具](./get-started-with-performance-tuning-and-optimization#compile-performance-tools)。

要为 htop 设置终端环境，请在设备上运行以下命令：

```text theme={null}
export TERM=xterm
```

```text theme={null}
htop
```

下图显示了该命令输出的每核心 CPU 使用情况：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-6.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=195b50f4887096639b9683943b059231" width="680" height="466" data-path="System/Performance/media/k2l-performance/fig-6-6.jpg" />
</div>

### **Trace Compass 中的 CPU 使用率**

1. 在主机上打开 Trace Compass 工具并加载跟踪。
2. 右键单击跟踪，然后选择 **Select Trace Type** > **Ftrace Format Type** > **Raw Textual Ftrace**，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-7.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=0621c1665e2ffd9c2739e2f753e3ea9a" width="901" height="375" data-path="System/Performance/media/k2l-performance/fig-6-7.jpg" />
</div>

3. 右键单击 **Raw Textual Ftrace** 并选择 **Open**。
4. 双击 **CPU usage** 查看系统级 CPU 使用率。在左侧面板中选择一个任务以检查每个任务的 CPU 使用率，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-8.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=c907e77f7e844e5fcdbb80c99870d51d" width="1055" height="274" data-path="System/Performance/media/k2l-performance/fig-6-8.jpg" />
</div>

### **监控用户空间应用的内存消耗**

您可以检查各种进程的内存分配和内存使用情况。

要检查某个进程的内存消耗，请在设备上运行以下命令：

```text theme={null}
cat /proc/<pid>/smaps_rollup
```

下图显示了该命令的输出：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-9.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=5310c5a54544168d34b51195fad30139" width="612" height="411" data-path="System/Performance/media/k2l-performance/fig-6-9.jpg" />
</div>

### **Procrank**

Procrank 是一款显示每个进程内存消耗的工具。默认情况下，它显示以下集大小：

* VSS：虚拟集大小（Virtual set size）
* RSS：常驻集大小（Resident set size）
* PSS：按比例集大小（Proportional set size）
* USS：独占集大小（Unique set size）

PSS 被视为进程的实际内存消耗。

### **从源代码构建 Procrank**

在主机上运行以下命令：

```text theme={null}
sudo apt install -y gcc-aarch64-linux-gnu
```

```text theme={null}
git clone https://github.com/cglmcu/procrank.git
```

```text theme={null}
cd procrank
```

```text theme={null}
export CC=aarch64-linux-gnu-gcc
```

```text theme={null}
aarch64-linux-gnu-gcc *.c -Os -o procrank -I.
```

ADB 包含在 Qualcomm Linux 构建中。要启用 ADB，请执行以下操作：

1. 启动设备。
2. 登录串口 shell。
3. 运行以下命令：
   ```text theme={null}
   touch /etc/usb-debugging-enabled
   ```
4. 要启动 ADB，请使用以下选项之一：
   * 选项 1：重启设备。
   * 选项 2：运行以下命令：
     ```text theme={null}
     systemctl start android-tools-adbd
     ```

启用后，除非删除 `/etc/usb-debugging-enabled` 文件并重启设备，否则 ADB 将保持活跃状态。

使用 Android Debug Bridge（adb）或类似工具将 Procrank 文件从主机传输到设备。示例命令如下：

```text theme={null}
adb shell mount -o remount, rw /usr
```

```text theme={null}
adb push procrank /usr/bin
```

```text theme={null}
adb shell chmod a+x /usr/bin/procrank
```

<Note>
  请确保在命令中指定目标 IP 地址。
</Note>

Procrank 命令示例：

* 要查看每个进程分配的匿名内存，请在设备上运行以下命令：
  ```text theme={null}
  procrank -C
  ```
* 要显示每个进程分配的文件缓存内存，请在设备上运行以下命令：
  ```text theme={null}
  procrank -c
  ```
* 要同时查看每个进程分配的匿名内存和文件缓存内存，请在设备上运行以下命令：
  ```text theme={null}
  procrank
  ```

下图显示了 `procrank -C` 命令的示例输出：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/rpHTx_a6zriKQll9/System/Performance/media/k2l-performance/fig-6-10.jpg?fit=max&auto=format&n=rpHTx_a6zriKQll9&q=85&s=839252f3ce4b421c0ec5dc9b59ed95a6" width="796" height="698" data-path="System/Performance/media/k2l-performance/fig-6-10.jpg" />
</div>

### **检查应用的每周期指令数**

perf 实用工具使用硬件性能计数器计算应用的每周期指令数（IPC）。

要编译 perf 实用工具，请参见[编译性能工具](./get-started-with-performance-tuning-and-optimization#compile-performance-tools)。

要计算 IPC，请在设备上运行以下命令：

```text theme={null}
perf stat -e cycles,instructions sleep 5
```

下图显示了该命令的示例输出：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-check-instruction-per-cycle-one.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=6bd1989cb3b09967dc6a456ce678780e" width="725" height="171" data-path="System/Performance/media/k2l-performance/fig-6-check-instruction-per-cycle-one.jpg" />
</div>

* 如果 IPC 小于 1.0，则很可能是内存受阻。在这种情况下，Qualcomm Linux 调优策略（例如减少内存 I/O 工作负载）有助于提升性能。
* 如果 IPC 大于 1.0，则很可能是指令受限。在这种情况下，通过消除不必要的工作和缓存操作来减少代码执行，有助于提升性能。

### **检查消耗 CPU 最多的代码部分**

perf 实用工具可以生成火焰图（flame graph），帮助可视化线程的栈以及在 CPU 上运行的所有函数的 CPU 使用情况。

要生成火焰图，请执行以下操作：

* 在设备上：
  1. 收集日志以生成火焰图。要使用 perf 实用工具收集日志，请运行以下命令：
     ```text theme={null}
     perf record -g -o /tmp/perf.data -p <process pid> sleep 5
     ```
     ```text theme={null}
     cd /tmp
     ```
     ```text theme={null}
     perf script > /tmp/perf.script
     ```
  2. 使用 SCP 或类似工具运行以下命令，将 `perf.script` 从目标设备传输到主机。请确保在命令中指定目标 IP 地址。示例命令如下：
     ```text theme={null}
     scp -r root@10.92.162.185:/tmp/perf.script /local/mnt/workspace/logs
     ```
* 在主机上：
  1. 运行以下命令下载火焰图：
     > > ```text theme={null}
     > > git clone https://github.com/brendangregg/FlameGraph.git
     > > ```
     >
     > 请确保在主机上安装 Perl。
  2. 将 `perf.script` 复制到 `FlameGraph` 目录中：
     > ```text theme={null}
     > cd FlameGraph
     > ```
     >
     > ```text theme={null}
     > perl stackcollapse-perf.pl perf.script > out.folded
     > ```
     >
     > ```text theme={null}
     > perl out.folded > perf.svg
     > ```
  3. 在浏览器中打开 SVG 文件查看火焰图，以了解 CPU 使用情况：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/rpHTx_a6zriKQll9/System/Performance/media/k2l-performance/fig-6-11.jpg?fit=max&auto=format&n=rpHTx_a6zriKQll9&q=85&s=39f1ccbc770f39e113ed6233c726fa6b" width="798" height="421" data-path="System/Performance/media/k2l-performance/fig-6-11.jpg" />
</div>

### **检查用户空间应用代码中各函数消耗的内存**

[Valgrind](https://valgrind.org/docs/manual/ms-manual.html) 是一款开源工具，提供了名为 massif 的实用工具，可帮助分析程序中每个函数消耗的内存。

以下是内存分配的示例代码：

```text theme={null}
    #include <stdlib.h>

void g(void) {
   malloc(4000);
}

void f(void) {
   malloc(2000);
   g();
}

int main(void) {
   int i;
   int* a[10];
   for (i = 0; i < 10; i++) {
      a[i] = malloc(1000);
   }
   f();
   g();
   for (i = 0; i < 10; i++) {
      free(a[i]);
   }
   return 0;
}
```

编译源代码并在设备上运行以下 Valgrind 命令：

```text theme={null}
valgrind --tool=massif ./test
```

以下是示例代码的输出：

cat massif.out.1587\
…\
n3: 20000 (heap allocation functions) malloc/new/new\[], --alloc-fns, etc.\
n0: 10000 0x10882B: main (in /home/root/valgrind/test)\
n2: 8000 0x1087E7: g (in /home/root/valgrind/test)\
n1: 4000 0x108807: f (in /home/root/valgrind/test)\
n0: 4000 0x10885B: main (in /home/root/valgrind/test)\
n0: 4000 0x10885F: main (in /home/root/valgrind/test)\
n1: 2000 0x108803: f (in /home/root/valgrind/test)\
n0: 2000 0x10885B: main (in /home/root/valgrind/test)

有关 Valgrind 的更多信息，请参见 [Valgrind User Manual](https://valgrind.org/docs/manual/ms-manual.html)。

### **检测用户空间应用中的内存泄漏**

要检测进程内的内存泄漏，可以使用启用了 leak-check 功能的 Valgrind 工具。

以下是已分配但未释放内存的示例代码：

```text theme={null}
    #include <stdlib.h>

void do_alloc() {
    int *x = malloc(10 * sizeof(int)); /* here simulate a leak */
    x[10] = 0; /* here write to invalid memory address */
}

int main() {
    do_alloc();
    return 0;
}
```

要检测内存泄漏，请编译示例代码并在设备上运行以下命令：

```text theme={null}
valgrind --leak-check=yes ./test
```

以下是示例代码的输出：

```text theme={null}
==1512== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1512== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1512== Command: ./test
==1512==
==1512== Invalid write of size 4
==1512==    at 0x1087B4: do_alloc (in /home/root/valgrind/test)
==1512==    by 0x1087CF: main (in /home/root/valgrind/test)
==1512==  Address 0x4a36068 is 0 bytes after a block of size 40 alloc'd
==1512==    at 0x486551C: malloc (vg_replace_malloc.c:381)
==1512==    by 0x1087A7: do_alloc (in /home/root/valgrind/test)
==1512==    by 0x1087CF: main (in /home/root/valgrind/test)
==1512==
==1512==
==1512== HEAP SUMMARY:
==1512==     in use at exit: 40 bytes in 1 blocks
==1512==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==1512==
==1512== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1512==    at 0x486551C: malloc (vg_replace_malloc.c:381)
==1512==    by 0x1087A7: do_alloc (in /home/root/valgrind/test)
==1512==    by 0x1087CF: main (in /home/root/valgrind/test)
```

## **高级排查**

高级排查方法用于系统层面。这些方法对于构建 Qualcomm 参考设备以及在所有层面集成 Qualcomm Linux 以生产最终产品至关重要。

有关相关信息，请参见[基本排查](#basic-troubleshooting)。

### **启动时间**

启动时间的各阶段和启动时间日志标记有助于调试和优化启动过程。

Qualcomm Linux 启动链可分为两个阶段：

* 引导加载程序初始化和内核加载：启动引导加载程序并加载内核。
* Linux 系统初始化：初始化内核、驱动和用户空间服务。

### **第一阶段时间线（引导加载程序初始化和内核加载）**

在设备启动序列期间，收集串口日志。解析这些日志可以更好地了解该阶段的里程碑。

可以使用下表列出的相应时间戳来测量各模块所花费的时间：

|   **模块**  |           **打印的调试行**          |
| :-------: | :---------------------------: |
| PBL + XBL |        "UEFI Start" 时间戳       |
| Core UEFI |  "UEFI Total" – 消耗的时间以毫秒为单位打印 |
|    内核加载   | "UEFI End" - OS Loader" 时间戳之差 |

有关如何收集串口日志的更多信息，请参见[测量启动时间](./performance-dashboards#measure-boot-time)。

以下是示例串口日志和时间线的示例：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-boot-time-example.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=197f9f6a48176c4e09715e54850e38d4" width="649" height="337" data-path="System/Performance/media/k2l-performance/fig-6-boot-time-example.jpg" />
</div>

### **第二阶段时间线（Linux 系统初始化）**

要捕获系统启动期间的性能统计信息，请使用 [systemd-analyze 工具](https://www.freedesktop.org/software/systemd/man/latest/systemd-analyze.html)。

要安装该工具，请参见[使用工具分析性能](./analyze-performance-with-tools)。

要分析内核中驱动的初始化情况，请在内核启动命令行中启用 `initcall_debug` 标志。使用 systemd-analyze 工具分析用户空间服务和应用的初始化详情。

以下是您可以在设备上运行以使用 systemd-analyze 工具的示例命令：

* 要获取内核和用户空间的启动时间，请运行以下命令：
  ```text theme={null}
  systemd-analyze time
  ```
  以下是该命令的输出：Linux QCS6490 (Linux 6.6.0 #1 SMP PREEMPT Sun Feb 4 18:35:47 UTC 2024) arm64. Startup finished in 4.238s (kernel) + 15.620s (userspace) = 19.859s multi-user.target reached after 15.594s in userspace
* 要获取启动期间每个子系统消耗的时间，请运行以下命令：
  ```text theme={null}
  systemd-analyze blame
  ```
  以下是该命令的输出：4.982s android-tools-adbd.service\
  3.013s dev-disk-byx2dpartlabel-system.device\
  1.418s systemd-modules-load.service\
  1.179s sshdgenkeys.service

### **系统初始化时间的图形视图**

`systemd-analyze plot` 命令提供已启动的系统服务及其初始化时间的图形化明细。

要获取系统服务的图形化明细，请在设备上运行以下命令：

```text theme={null}
systemd-analyze plot > /var/lib/systemd-plot.svg
```

要可视化系统初始化阶段各模块的时间消耗并分析性能，请在任意 Web 浏览器中打开 `systemd-plot.svg` 文件。下图显示了示例图表：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-12.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=066f44e8dce8a7d06e13ea5781d75c00" width="1497" height="810" data-path="System/Performance/media/k2l-performance/fig-6-12.jpg" />
</div>

### **识别 CPU 受限的用例**

要验证任务是否在能力最强的 CPU 上以最大频率运行，请捕获调度器和频率 ftrace。

以下是使用 `while` 循环加载 CPU 的示例代码：

```text theme={null}
    #include <stdlib.h>
#include <unistd.h>

int main() {
     int i = 0;
     while(1)
    {
        i++;
    }
    return 0;
  }
```

您可以为示例代码收集 ftrace，并使用 Trace Compass 加载 ftrace。这样可以检查测试线程是否以 2.7 GHz 的最大 CPU 频率在 Prime 核心上运行，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-13.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=cbfe3b4baa3f6746b4773cf68c30f06c" width="1688" height="684" data-path="System/Performance/media/k2l-performance/fig-6-13.jpg" />
</div>

### **识别 I/O 受限的用例**

要获取 I/O 统计信息，请使用 `/proc/diskstats`。

更多信息请参见 [/proc/diskstats](https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats)。

以下是在设备上针对 I/O 受限用例运行 lmdd 的示例：

* 在运行用例之前，运行以下命令：
  ```text theme={null}
  cat /proc/diskstats
  ```
  以下是该命令的输出：8 10 sda10 715 544 15056 250 4394 413 4199944 135729 0 5508 135979 0 0 0 0 0 0 接下来，从 vmstat 获取 `pgpgin` 和 `pgpgout`：
  ```text theme={null}
  cat /proc/vmstat
  ```
  以下是该命令的输出：pgpgin 348632pgpgout 2100056
* 要运行 lmdd，必须先编译 lmbench，更多信息请参见[编译性能工具](./get-started-with-performance-tuning-and-optimization#compile-performance-tools)。对于 I/O 受限的用例，运行以下 lmdd 命令：
  ```text theme={null}
  lmdd if=/mnt/overlay/2GB.file of=/mnt/overlay/2GB.file.copy fsync=1 bs=1M
  ```
* 运行用例后，运行以下命令：
  ```text theme={null}
  cat /proc/diskstats
  ```
  以下是该命令的输出：8 10 sda10 4822 544 4209448 13018 8530 451 8394624 300094 0 11836 313112 0 0 0 0 0 0
* 接下来，再次检查 `pgpgin` 和 `pgpgout`：
  ```text theme={null}
  cat /proc/vmstat
  ```
  以下是该命令的输出：pgpgin 2446172pgpgout 4197396

以下是 I/O 受限用例的统计信息示例：

```text theme={null}
Sectors read = (4209448 – 15056) = 4194392 sectors = 2GB
Time spent reading = (13018 – 250) = 12768 ms
Sectors written = (8394624 - 4199944) = 4194680 sectors = 2GB
Time spent writing = (300094 -135729) = 164365 ms
Time spend IO = (11836 – 5508) = 6328 ms

pgpgin gap = (2446172-348632) = 2GB
pgpgout gap = (4197396 – 2100056) = 2GB
```

更多信息请参见 [I/O statistics fields](https://www.kernel.org/doc/Documentation/iostats.txt)。

### **Vmstat**

`Vmstat` 是一个 Linux 命令，用于收集块输入（bi）和块输出（bo）的信息。下图显示了 vmstat 输出的示例：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-14.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=c1415bfa1543117d910418d622917026" width="694" height="255" data-path="System/Performance/media/k2l-performance/fig-6-14.jpg" />
</div>

更多信息请参见 [Transparent Hugepage Support](https://www.kernel.org/doc/html/latest/admin-guide/mm/transhuge.html)。

### **对重负载用例使用大核心**

当重负载任务以较长运行时间在 Silver 核心上运行时，可能会影响性能。使用 `sched_setaffinity()` 将此类任务亲和到较大的（Gold）核心。这种任务亲和有助于减少 CPU 运行时间并提升性能。

<Warning>
  对节点的任何修改都可能影响设备的功耗和性能。在更改节点之前，务必在所有相关用例中验证其影响。
</Warning>

下图来自 Trace Compass，显示了一个测试线程在 CPU0 上以 1.9 GHz 的频率运行 12.9 毫秒的示例。

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-15.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=4f3e4a48fef5eeef43431967f55bf45e" width="926" height="375" data-path="System/Performance/media/k2l-performance/fig-6-15.jpg" />
</div>

要使用 `sched_setaffinity()` 将任务亲和性设置到 Gold 核心，请参见 [sched\_setaffinity(2) — Linux manual page](https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html)。

以下是将任务亲和到 Gold 核心 7 的示例代码：

```text theme={null}
#include <sched.h>
#include <unistd.h>
#include <sys/syscall.h>
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(7, &mask);
pid_t tid = syscall(__NR_gettid);
int result = sched_setaffinity(tid, sizeof(mask), &mask);
```

使用 `sched_setaffinity()` 设置任务亲和性后，该任务在 CPU7 上运行，运行时间从 12.9 毫秒减少到 2.9 毫秒，CPU 频率为 2.7 GHz。

下图显示了设置 `sched_setaffinity()` 属性后减少的时间：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-16.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=cc59ad1297277adfa5349f2cc9cdc66a" width="948" height="296" data-path="System/Performance/media/k2l-performance/fig-6-16.jpg" />
</div>

### **减轻可运行状态（runnable）对用例的影响**

当任务已准备好运行但 CPU 不可用时，该任务被视为处于可运行（runnable）状态。当 CPU 负载较重时，任务会被赋予此状态。

要可视化线程的状态，可以使用 Trace Compass 的 **Control Flow** 视图。

下图以不同颜色显示线程状态：

* 深红色线表示线程处于可运行状态
* 黄色线表示休眠状态
* 红色线表示 CPU 正忙于处理 `irq` 或 `softirq`

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/mitigate_impact_runnables.png?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=288ede338b9a46c2ed907fc631aa0967" width="1416" height="316" data-path="System/Performance/media/k2l-performance/mitigate_impact_runnables.png" />
</div>

可运行状态的类型：

* 唤醒延迟型可运行是指已就绪的任务从可运行状态到实际在 CPU 上运行所需的时间。可以通过调优调度器或禁用 CPU 的低功耗模式来减少此延迟。
* 普通可运行发生在 CPU 选择更高优先级的进程运行而非当前进程时。提高任务的优先级有助于减少可运行状态。

线程的优先级取决于其类型：

* 实时（RT）线程的优先级范围为 0 到 99，数字越大表示优先级越高。要更改实时线程优先级，请在 `sched_setscheduler()` 中使用 `SCHED_FIFO` 策略。
* 普通线程的优先级范围为 100 到 139，数字越小表示优先级越高。要更改普通线程优先级，请使用 `renice` Linux 命令以及带 `SCHED_OTHER` 策略的 `sched_setscheduler()`。–20 到 +19 范围内的值映射到 100 到 139 范围内的线程优先级。

要通过更改线程优先级来减少可运行时间，请使用 `sched_setscheduler()`。

有关 `sched_setscheduler()`，请参见 [sched\_setscheduler(2)—Linux manual page](https://man7.org/linux/man-pages/man2/sched_setscheduler.2.html)。

以下是通过使用 `sched_setscheduler()` 更改线程优先级来减少可运行时间的示例代码：

```text theme={null}
struct sched_param param = {0};
param.sched_priority = 1;
int ret=0;
ret = sched_setscheduler(0, SCHED_FIFO, &param);
```

第一个参数表示任务 ID。0 表示当前任务。第二个参数表示调度器策略。`SCHED_FIFO` 用于 RT 线程。`sched_priority` 等于 1。

```text theme={null}
0--> 99 ( RT class highest priority)
1 --> 99-1 --> 98
2 --> 99-2 --> 97
..
99 --> 99-99 --> 0 (RT least priority)
```

默认情况下，进程优先级为 120。它从 shell 继承。可运行时间为 225 毫秒，运行时间为 267 毫秒。将进程优先级从 120 提高到 98（实时优先级）后，可运行持续时间减少到 2 毫秒以内。

### **加速 CPU 频率爬升时间**

延迟切换到所需的更高 CPU 频率会影响性能。您可以调优 `sched_util_clamp_min` 调度器节点以加速 CPU 频率爬升。

在 0 到 1024 的范围内调优 `sched_util_clamp_min`。较高的值可以提升性能，但也可能增加功耗。

以下是测试线程在核心 4 上运行表现的示例：

* 当 `sched_util_clamp_min` 为 0 时，CPU 频率从 691 MHz 缓慢爬升到 1.5 GHz，然后再到 1.7 GHz。您可以在设备上运行以下命令来设置此值：
  ```text theme={null}
  echo 0 > /proc/sys/kernel/sched_util_clamp_min
  ```
  下图来自 Trace Compass，显示了 CPU 频率的爬升：
  <div className="flex flex-col items-center gap-1">
    <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-18.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=dd710934392853cb2eeeec4715bd9a19" width="1231" height="99" data-path="System/Performance/media/k2l-performance/fig-6-18.jpg" />
  </div>
* 当 `sched_util_clamp_min` 为 512 时，CPU 频率从 691 MHz 直接爬升到 1.9 GHz。您可以在设备上运行以下命令来设置此值：
  ```text theme={null}
  echo 512 > /proc/sys/kernel/sched_util_clamp_min
  ```
  下图显示了 CPU 频率爬升到 1.9 GHz：
  <div className="flex flex-col items-center gap-1">
    <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-19.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=c6998371206dcfc33fb4025a459ddab0" width="1178" height="98" data-path="System/Performance/media/k2l-performance/fig-6-19.jpg" />
  </div>
* 当 `sched_util_clamp_min` 为 1024 时，CPU 频率从 691 MHz 直接爬升到 2.4 GHz 的最大频率（FMAX）。您可以在设备上运行以下命令来设置此值：
  ```text theme={null}
  echo 1024 > /proc/sys/kernel/sched_util_clamp_min
  ```
  下图显示了 CPU 频率从 691 MHz 直接爬升到 FMAX 2.4 GHz：
  <div className="flex flex-col items-center gap-1">
    <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-20.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=a4294427aabec5234759756ed512a3ae" width="947" height="102" data-path="System/Performance/media/k2l-performance/fig-6-20.jpg" />
  </div>

### **确定用例的缓存驻留情况**

perf 实用工具用于分析缓存未命中和缓存重填计数器统计信息。此分析有助于确定用例在特定缓存中的驻留情况，例如 L2、L3 和末级缓存控制器（LLCC）DDR 驻留。

有关如何编译 perf 实用工具的说明，请参见[编译性能工具](./get-started-with-performance-tuning-and-optimization#compile-performance-tools)。

要检查目标可用的缓存事件，请在设备上运行以下命令：

```text theme={null}
perf list | grep cache
```

以下是获取缓存驻留情况的示例命令：

```text theme={null}
perf stat -e l1d_cache_lmiss_rd -e l1i_cache_lmiss -e l2d_cache_lmiss_rd -e l3d_cache_lmiss_rd -e ll_cache_miss_rd  sleep 5
```

CPU 路径中来自前级缓存（L1 → L2 → L3 → LLCC → DDR）的缓存未命中计数器表示用例在后续缓存中的驻留情况。

以下示例代码提供了缓存未命中计数器统计信息：

```text theme={null}
Performance counter stats for '5 duration':

           5797      l1d_cache_lmiss_rd
          26699      l1i_cache_lmiss
          16200      l2d_cache_lmiss_rd
           8634      l3d_cache_lmiss_rd
           9710      ll_cache_miss_rd

    5.004388332 seconds time elapsed

    0.001599000 seconds user
    0.000000000 seconds sys
```

### **识别锁争用**

当一个线程（thread\_1）尝试获取已被另一个线程（thread\_2）持有的互斥锁（Mutex）时，就会发生锁争用。

在这种情况下，thread\_1 进入休眠模式，并在 thread\_2 释放互斥锁时被唤醒。

要解决此问题，请转到 **Trace Compass** 并选择 **Select Previous State Change**，如下图所示：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-21.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=64c79ee8206720df21e8c70d8d9484e9" width="280" height="96" data-path="System/Performance/media/k2l-performance/fig-6-21.jpg" />
</div>

下图显示了线程 2991 唤醒线程 2993 的实例：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-22.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=873a7ff72d7cb180b0a1dc4e286ed78a" width="983" height="378" data-path="System/Performance/media/k2l-performance/fig-6-22.jpg" />
</div>

### **确定抢占禁用的持续时间**

内核以抢占方式运行。这意味着任何内核进程都可能随时被暂停，以让位于更高优先级的进程。因此，新任务可以在先前任务被抢占的同一临界区中开始运行。

以下步骤概述了如何记录抢占被禁用的持续时间：

1. 在内核配置中，在源代码中启用 `CONFIG_IRQSOFF_TRACER` 和 `CONFIG_PREEMPT_TRACER`。
2. 要收集跟踪，请运行以下命令：**注意** 以下命令应在设备上运行。
   ```text theme={null}
   echo preemptoff > /sys/kernel/tracing/current_tracer
   ```
   ```text theme={null}
   echo 1 > /sys/kernel/tracing/tracing_on
   ```
   ```text theme={null}
   cat /sys/kernel/tracing/trace
   ```

如图所示，每次抢占被禁用时都会记录一个时间戳，标记代码中的起点和终点：

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-print-timestamp.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=6f9d2270984aea8ee9d4e9eef18cb34d" width="643" height="452" data-path="System/Performance/media/k2l-performance/fig-6-print-timestamp.jpg" />
</div>

有关函数跟踪器的更多信息，请参见 [ftrace - Function Tracer](https://www.kernel.org/doc/Documentation/trace/ftrace.txt)。

### **调试丢帧**

丢帧可能是由于各种子系统（如显示或摄像头）的延迟造成的。例如，如果显示刷新率为 60 Hz，则每帧必须在 16.6 毫秒内完成。

下图显示了一个跟踪，其中 `Weston` 和 `SDM_EventThread` 每 16.6 毫秒运行一次。任何应用都必须周期性地渲染，并在这 16.6 毫秒的时间窗口内完成渲染。如果在此窗口到期前渲染未完成，则会丢帧。

<div className="flex flex-col items-center gap-1">
  <img src="https://mintcdn.com/qualcomm-prod/OKFyShYzKWv2bmj8/System/Performance/media/k2l-performance/fig-6-23.jpg?fit=max&auto=format&n=OKFyShYzKWv2bmj8&q=85&s=20e53323eccbff9b95e1bfa0f7538d75" width="896" height="174" data-path="System/Performance/media/k2l-performance/fig-6-23.jpg" />
</div>

### **识别内存颠簸（memory thrashing）**

当系统花费大量时间从 RAM 中回收内存，然后又将相同内容重新加载回 RAM 时，就会发生内存颠簸。

这可能发生在来自磁盘的文件缓存页和来自 ZRAM 的匿名页上，导致性能大幅下降。

内存颠簸通常发生在可用内存不足以满足当前用例（称为工作集，workingset）时。这会导致系统难以找到可回收的内存。

您可以通过 `/proc/vmstat` 中的以下信息识别内存颠簸：

|                     **vmstat 节点**                     |                                               **说明**                                               |
| :---------------------------------------------------: | :------------------------------------------------------------------------------------------------: |
|  `workingset_refault_anon`/`workingset_refault_file`  |                                  这些节点表示回收后立即被再次请求的已回收页数量。这些数字越低越好。                                 |
| `workingset_activate_anon`/`workingset_activate_file` |                                   这些节点表示回收后立即被激活的已回收页数量。这些数字越低越好。                                  |
|                   `pgpgin`/`pswpin`                   |                                    这些节点表示从交换空间读取并换回 RAM 内存的页数量。                                    |
|                  `pgpgout`/`pswpout`                  |       这些节点表示作为回收的一部分写入交换空间的页数量。如果 `pgpg*` 和 `pswp*` 与 `workingset_refaults` 同时增加，则表明存在内存颠簸情况。      |
|           `pgsteal_kswapd`/`pgsteal_direct`           |                                           这些节点表示系统回收的页数量。                                          |
|            `pgscan_kswapd`/`pgscan_direct`            | 这些节点表示系统为查找可回收内存而扫描的页数量。`pgsteal`/`pgscan` 的比率表示系统的回收效率。值越高表示系统性能越好，而回收效率越低表示系统难以找到可回收的内存，即存在内存颠簸。 |

要识别内存颠簸，请在设备上运行以下命令：

```text theme={null}
cat /proc/vmstat
```

vmstat 字段如下：

```text theme={null}
workingset_refault_anon 984111
workingset_refault_file 1838690
workingset_activate_anon 502428
workingset_activate_file 499034
pgpgin 17488312
pgpgout 3398036
pswpin 984141
pswpout 2101230
pgsteal_kswapd 3946686
pgsteal_direct 59226
pgscan_kswapd 4660928
pgscan_direct 73719
```

这些计数器随时间线性增加。

要检测内存颠簸的模式，请定期从这些计数器收集数据。然后，将这些数据在特定时间段内绘制成图，以可视化模式。

## **后续步骤**

* [性能仪表板](./performance-dashboards)
