Skip to content

性能剖析(Profile)

本文档介绍在 ROS 2/Nav2 中对应用程序进行性能剖析(profiling)的方法。性能剖析会生成可分析的数据文件,帮助你了解程序运行期间的计算时间和资源消耗分布在哪里,从而定位瓶颈、发现可改进之处。

以下步骤说明如何调整 Nav2 技术栈,以获取特定服务器或算法的剖析数据。本教程同样适用于仿真机器人和实体机器人。

本教程使用两个工具:Valgrind 工具集中的 callgrind 和 kcachegrind。Valgrind 用于采集程序的运行时数据,kcachegrind 则用于可视化分析这些数据。

Terminal window
sudo apt install valgrind kcachegrind

更多信息请参阅 Valgrind 手册,其中还介绍了可用于指定更多信息的其他 valgrind 参数。

使用 valgrind 需要带调试信息编译,可以通过传入 -g 编译选项,或将 CMAKE_BUILD_TYPE 设为 Debug 或 RelWithDebInfo 来实现。然后用 valgrind 运行程序,捕获运行时统计信息供后续分析。统计信息保存在 callgrind.out.XXX 文件中,后缀为进程的 PID。最后用 kcachegrind 可视化分析程序的执行结果。

CMakeLists.txt
add_compile_options(-g)
Terminal window
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
Terminal window
valgrind --tool=callgrind [your-program] [program options]
kcachegrind callgrind.out.XXX

与通用示例一样,对于给定的节点,需要用调试标志编译,以便 Valgrind 采集剖析信息。这可以直接在命令行完成。注意我们使用 --packages-select 仅编译需要剖析的软件包。

Terminal window
colcon build --packages-select <packages of interest> --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo

也可以在要剖析的软件包的 CMakeLists.txt 中添加下面这行。当工作空间包含大量软件包,但只想在一次 colcon build 调用中编译其中部分带调试信息的软件包时,这种方式更方便。

如果需要获取某个插件的运行时剖析结果,这行代码必须同时添加到宿主服务器和插件各自的软件包中。

add_compile_options(-pg)

采用任一编译方法后,该节点应单独在自己的终端中运行,以便与系统其余部分隔离,因此不应与其他节点组合(compose)在同一进程中。用 valgrind 运行 ROS 2 节点,可以在命令行这样做:

Terminal window
ros2 run --prefix 'valgrind --tool=callgrind' <pkg> <node> --all-other-launch arguments

例如,在加载了特定控制器插件的情况下对控制器服务器进行剖析。nav2_controller 和目标插件所在的软件包都需要用调试标志编译。下面的例子运行一个带话题重映射和参数文件路径的 ROS 2 节点:

Terminal window
ros2 run --prefix 'valgrind --tool=callgrind' nav2_controller controller_server --ros-args -r __node:=controller_server -r cmd_vel:=cmd_vel_nav --params-file /path/to/nav2_bringup/params/nav2_params.yaml

收集到足够的数据后,用 Control+C 干净地退出进程。

与节点示例一样,从启动文件剖析节点时也需要用调试标志编译。可以在启动文件内通过启动前缀(launch prefix)完成与命令行相同的 valgrind 调用。

如之前的示例,下面演示如何在启动文件内启动 controller_server 节点。

start_controller_server_node = Node(
parameters=[
get_package_share_directory("nav2_bringup") + '/params/nav2_params.yaml',
{'use_sim_time': use_sim_time}
],
package='nav2_controller',
executable='controller_server',
name='controller_server',
prefix=['xterm -e valgrind --tool=callgrind'],
output='screen')

和之前一样,此进程需要与其他进程隔离。因此在剖析特定节点时,该启动文件中不应运行其他节点,也不应使用节点组合。

收集到足够的数据后,用 Control+C 干净地退出进程。

Nav2 bringup 的每个启动文件包含多个节点(当 use_composition=true 时,每个进程包含多个节点),因此需要将待剖析的特定节点从系统其余部分中分离出来。如前所述,将其隔离到单独的启动文件中或通过命令行启动后,即可方便地采集 callgrind 数据。

在 Nav2 中的操作步骤如下:

  • 从 navigation_launch.py 中移除服务器节点,确保同时移除文件中组合和非组合的选项
  • 在单独的启动文件中或使用 ros2 run CLI,按上述说明启动待剖析的节点
  • 照常启动缺少该节点的 Nav2
  • 数据采集完成后,按 Control+C 干净地结束被剖析的进程以及导航的其余部分

剖析节点必须在 Nav2 之前启动,才能接收生命周期管理器(lifecycle manager)的信号以完成状态转换。

获得 callgrind 结果后(无论通过节点、启动文件、Nav2 还是其他方式获取),即可分析剖析结果,识别瓶颈或潜在的改进点。使用 kcachegrind:

Terminal window
kcachegrind callgrind.out.XXX

这会打开一个类似下图的窗口。左侧显示所有的调用及其(包括子函数在内的)计算时间相对百分比。

kcachegrind

如果选中左侧边栏的顶层条目,然后在右侧工作区底部选择「Call Graph」(调用图),它会以方法调用图的形式展示计算时间花在了哪里,对找到耗时最多的方法非常有帮助。

call_graph