目标:本教程将展示在编写 ROS 2 C++ 代码时如何使用自定义内存分配器。
教程级别:高级
时间:20 分钟
目录
背景
编写分配器
编写一个示例主程序
将分配器传递到进程内管道
测试和验证代码
TLSF 分配器
本教程将教您如何为发布者和订阅者集成自定义分配器,以便在您的 ROS 节点执行时从不调用默认堆分配器。本教程的代码可在此处获得。https://github.com/ros2/demos/blob/jazzy/demo_nodes_cpp/src/topics/allocator_tutorial_pmr.cpp
背景
假设您想编写实时安全代码,并且您听说过在实时关键部分调用 new
的许多危险,因为大多数平台上的默认堆分配器是非确定性的。
默认情况下,许多 C++ 标准库结构在增长时会隐式分配内存,例如 std::vector
。然而,这些数据结构也接受一个“分配器Allocator”模板参数。如果您为这些数据结构之一指定一个自定义分配器,它将使用该分配器而不是系统分配器来增长或缩小数据结构。您的自定义分配器可以在堆栈上预先分配一块内存池,这可能更适合实时应用程序。
在 ROS 2 C++ 客户端库 (rclcpp) 中,我们遵循与 C++ 标准库类似的理念。发布者Publishers、订阅者subscribers和执行器Executor接受一个分配器模板参数,该参数控制该实体在执行期间进行的分配。
编写一个分配器
要编写与 ROS 2 的分配器接口兼容的分配器,您的分配器必须与 C++ 标准库分配器接口兼容。
自 C++17 起,标准库提供了一种称为 std::pmr::memory_resource
的东西。这是一个可以派生的类,用于创建满足最低要求的自定义分配器。
例如,以下自定义内存资源的声明满足要求(当然,您仍然需要在此类中实现声明的函数):
这是一个自定义内存资源类 CustomMemoryResource
,继承自 std::pmr::memory_resource
。该类重写了三个方法:
do_allocate
:用于分配指定大小和对齐方式的内存。do_deallocate
:用于释放指定大小和对齐方式的内存。do_is_equal
:用于比较两个内存资源是否相等。
// 定义一个名为 CustomMemoryResource 的类,继承自 std::pmr::memory_resource
class CustomMemoryResource : public std::pmr::memory_resource
{
private:
// 重写 do_allocate 方法,用于分配内存
void * do_allocate(std::size_t bytes, std::size_t alignment) override;
// 重写 do_deallocate 方法,用于释放内存
void do_deallocate(
void * p, std::size_t bytes,
std::size_t alignment) override;
// 重写 do_is_equal 方法,用于比较两个内存资源是否相等
bool do_is_equal(
const std::pmr::memory_resource & other) const noexcept override;
};
要了解 std::pmr::memory_resource
的全部功能,请参见 https://en.cppreference.com/w/cpp/memory/memory_resource。
此教程的自定义分配器的完整实现位于 https://github.com/ros2/demos/blob/jazzy/demo_nodes_cpp/src/topics/allocator_tutorial_pmr.cpp。
编写示例主程序
一旦你编写了一个有效的 C++分配器,你必须将其作为共享指针传递给你的发布者、订阅者和执行器。但首先,我们将声明一些别名以缩短名称。
// 使用命名空间简化代码
using rclcpp::memory_strategies::allocator_memory_strategy::AllocatorMemoryStrategy;
// 定义一个多态分配器类型
using Alloc = std::pmr::polymorphic_allocator<void>;
// 定义消息分配器特性
using MessageAllocTraits =
rclcpp::allocator::AllocRebind<std_msgs::msg::UInt32, Alloc>;
// 定义消息分配器类型
using MessageAlloc = MessageAllocTraits::allocator_type;
// 定义消息删除器类型
using MessageDeleter = rclcpp::allocator::Deleter<MessageAlloc, std_msgs::msg::UInt32>;
// 定义消息唯一指针类型
using MessageUniquePtr = std::unique_ptr<std_msgs::msg::UInt32, MessageDeleter>;
现在我们可以使用自定义分配器创建我们的资源:如何创建自定义内存资源,并将其用于ROS 2节点的发布者和订阅者。通过设置自定义内存分配器,可以在程序执行期间跟踪内存分配和释放的次数。
CustomMemoryResource mem_resource{}; // 创建自定义内存资源对象
auto alloc = std::make_shared<Alloc>(&mem_resource); // 创建共享指针分配器
rclcpp::PublisherOptionsWithAllocator<Alloc> publisher_options; // 定义发布者选项
publisher_options.allocator = alloc; // 设置发布者选项的分配器
auto publisher = node->create_publisher<std_msgs::msg::UInt32>(
"allocator_tutorial", 10, publisher_options); // 创建发布者
rclcpp::SubscriptionOptionsWithAllocator<Alloc> subscription_options; // 定义订阅者选项
subscription_options.allocator = alloc; // 设置订阅者选项的分配器
auto msg_mem_strat = std::make_shared<
rclcpp::message_memory_strategy::MessageMemoryStrategy<
std_msgs::msg::UInt32, Alloc>>(alloc); // 创建消息内存策略
auto subscriber = node->create_subscription<std_msgs::msg::UInt32>(
"allocator_tutorial", 10, callback, subscription_options, msg_mem_strat); // 创建订阅者
std::shared_ptr<rclcpp::memory_strategy::MemoryStrategy> memory_strategy =
std::make_shared<AllocatorMemoryStrategy<Alloc>>(alloc); // 创建内存策略
rclcpp::ExecutorOptions options; // 定义执行器选项
options.memory_strategy = memory_strategy; // 设置执行器选项的内存策略
rclcpp::executors::SingleThreadedExecutor executor(options); // 创建单线程执行器
您还必须实例化一个自定义删除器和分配器,以便在分配消息时使用.如何定义消息删除器和消息分配器,并将分配器设置为消息删除器的分配器。这样,当消息被删除时,将使用自定义内存分配器进行内存释放
MessageDeleter message_deleter; // 定义消息删除器
MessageAlloc message_alloc = *alloc; // 定义消息分配器
rclcpp::allocator::set_allocator_for_deleter(&message_deleter, &message_alloc); // 设置消息删除器的分配器
一旦将节点添加到执行器中,就该开始旋转了。我们将使用自定义分配器来分配每条消息:
uint32_t i = 0; // 定义计数器
while (rclcpp::ok()) { // 当ROS 2运行正常时循环
auto ptr = MessageAllocTraits::allocate(message_alloc, 1); // 使用自定义分配器分配消息
MessageAllocTraits::construct(message_alloc, ptr); // 构造消息
MessageUniquePtr msg(ptr, message_deleter); // 创建消息唯一指针
msg->data = i; // 设置消息数据
++i; // 增加计数器
publisher->publish(std::move(msg)); // 发布消息
rclcpp::sleep_for(10ms); // 休眠10毫秒
executor.spin_some(); // 执行一些回调
}
将分配器传递到进程内管道
尽管我们在同一进程中实例化了发布者和订阅者,但我们还没有使用进程内管道。
IntraProcessManager 是一个通常对用户隐藏的类,但为了向其传递自定义分配器,我们需要通过从 rclcpp Context 获取它来公开它。IntraProcessManager 使用了几个标准库结构,因此如果没有自定义分配器,它将调用默认的 new
。
auto context = rclcpp::contexts::get_global_default_context(); // 获取全局默认上下文
auto options = rclcpp::NodeOptions()
.context(context) // 设置节点选项的上下文
.use_intra_process_comms(true); // 启用进程内通信
auto node = rclcpp::Node::make_shared("allocator_example", options); // 创建节点
代码片段展示了如何获取全局默认上下文,并使用该上下文和进程内通信选项创建一个ROS 2节点。通过启用进程内通信,可以在同一进程内的节点之间高效地传递消息。
确保在以这种方式构建节点后实例化发布者和订阅者。
测试和验证代码
你怎么知道你的自定义分配器实际上被调用了?
显而易见的做法是计算对自定义分配器的 allocate
和 deallocate
函数的调用次数,并将其与对 new
和 delete
的调用次数进行比较。
将计数添加到自定义分配器很容易:
void * do_allocate(std::size_t size, std::size_t alignment) override
{
// ...
num_allocs++;
// ...
}
void do_deallocate(
void * p, std::size_t bytes,
std::size_t alignment) override
{
// ...
num_deallocs++;
// ...
}
你也可以重载全局的 new
和 delete
操作符:
// 重载全局new操作符
void * operator new(std::size_t size)
{
if (is_running) { // 如果程序正在运行
global_runtime_allocs++; // 增加全局分配次数
}
return std::malloc(size); // 使用malloc分配内存
}
// 重载全局delete操作符,带大小参数
void operator delete(void * ptr, size_t) noexcept
{
if (ptr != nullptr) { // 如果指针不为空
if (is_running) { // 如果程序正在运行
global_runtime_deallocs++; // 增加全局释放次数
}
std::free(ptr); // 使用free释放内存
}
}
// 重载全局delete操作符,不带大小参数
void operator delete(void * ptr) noexcept
{
if (ptr != nullptr) { // 如果指针不为空
if (is_running) { // 如果程序正在运行
global_runtime_deallocs++; // 增加全局释放次数
}
std::free(ptr); // 使用free释放内存
}
}
我们正在递增的变量只是全局静态整数,而 is_running
是一个全局静态布尔值,在调用 spin
之前会被切换。
构建
cxy@ubuntu2404-cxy:~/second_ros2_ws$ colcon build --packages-select demo_nodes_cpp
[0.398s] WARNING:colcon.colcon_core.package_selection:Some selected packages are already built in one or more underlay workspaces:
'demo_nodes_cpp' is in: /home/cxy/ros2_jazzy/install/demo_nodes_cpp
If a package in a merged underlay workspace is overridden and it installs headers, then all packages in the overlay must sort their include directories by workspace order. Failure to do so may result in build failures or undefined behavior at run time.
If the overridden package is used by another package in any underlay, then the overriding package in the overlay must be API and ABI compatible or undefined behavior at run time may occur.
If you understand the risks and want to override a package anyways, add the following to the command line:
--allow-overriding demo_nodes_cpp
This may be promoted to an error in a future release of colcon-override-check.
Starting >>> demo_nodes_cpp
[Processing: demo_nodes_cpp]
[Processing: demo_nodes_cpp]
Finished <<< demo_nodes_cpp [1min 3s]
Summary: 1 package finished [1min 3s]
示例可执行文件打印变量的值。要运行示例可执行文件,请使用:
ros2 run demo_nodes_cpp allocator_tutorial
或者,使用内部进程管道运行示例:
ros2 run demo_nodes_cpp allocator_tutorial intra
你应该得到如下数字:
我们已经捕捉到了执行路径上大约 2/3 的分配/释放,但剩下的 1/3 来自哪里?
事实上,这些分配/释放源自本例中使用的底层 DDS 实现。
证明这一点超出了本教程的范围,但您可以查看作为 ROS 2 持续集成测试的一部分运行的分配路径测试,该测试通过代码回溯并确定某些函数调用是源自 rmw 实现还是 DDS 实现
https://github.com/ros2/realtime_support/blob/jazzy/tlsf_cpp/test/test_tlsf.cpp#L41
请注意,此测试未使用我们刚刚创建的自定义分配器,而是使用了 TLSF 分配器(见下文)。
TLSF 分配器
ROS 2 提供对 TLSF(两级分离适配)分配器的支持,该分配器旨在满足实时要求
https://github.com/ros2/realtime_support/tree/jazzy/tlsf_cpp
有关 TLSF 的更多信息,请参见 http://www.gii.upv.es/tlsf/
请注意,TLSF 分配器是根据双重 GPL/LGPL 许可证授权的。
一个使用 TLSF 分配器的完整工作示例在这里:https://github.com/ros2/realtime_support/blob/jazzy/tlsf_cpp/example/allocator_example.cpp
// 版权声明
// 2015年开放源代码机器人基金会版权所有
//
// 根据Apache许可证2.0版(“许可证”)授权;
// 除非符合许可证,否则您不得使用此文件。
// 您可以在以下网址获取许可证副本:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 除非适用法律要求或书面同意,
// 根据许可证分发的软件按“原样”分发,
// 不提供任何明示或暗示的担保或条件。
// 有关许可证下特定语言的权限和限制,请参阅许可证。
#include <list> // 包含列表库
#include <memory> // 包含内存管理库
#include <string> // 包含字符串库
#include <utility> // 包含实用工具库
#include "rclcpp/rclcpp.hpp" // 包含ROS 2核心库
#include "rclcpp/strategies/allocator_memory_strategy.hpp" // 包含ROS 2分配器内存策略库
#include "std_msgs/msg/u_int32.hpp" // 包含标准消息库
#include "tlsf_cpp/tlsf.hpp" // 包含TLSF内存分配库
// 定义TLSF分配器模板
template<typename T>
using TLSFAllocator = tlsf_heap_allocator<T>;
int main(int argc, char ** argv)
{
// 使用命名空间简化代码
using rclcpp::memory_strategies::allocator_memory_strategy::AllocatorMemoryStrategy;
using Alloc = TLSFAllocator<void>;
rclcpp::init(argc, argv); // 初始化ROS 2
rclcpp::Node::SharedPtr node; // 定义共享指针节点
// 定义命令行参数列表
std::list<std::string> keys = {"intra", "intraprocess", "intra-process", "intra_process"};
bool intra_process = false; // 标记是否使用进程内通信
// 检查命令行参数是否包含进程内通信的关键字
if (argc > 1) {
for (auto & key : keys) {
if (std::string(argv[1]) == key) {
intra_process = true; // 如果命令行参数匹配,启用进程内通信
break;
}
}
}
// 根据是否启用进程内通信设置节点选项
if (intra_process) {
printf("进程内管道已开启。\n");
auto options = rclcpp::NodeOptions()
.use_intra_process_comms(true);
node = rclcpp::Node::make_shared("allocator_example", options);
} else {
printf("进程内管道已关闭。\n");
auto options = rclcpp::NodeOptions()
.use_intra_process_comms(false);
node = rclcpp::Node::make_shared("allocator_example", options);
}
uint32_t counter = 0; // 定义计数器
auto callback = &counter -> void
{
(void)msg;
++counter; // 增加计数器
};
// 创建自定义分配器并将其传递给发布者和订阅者。
auto alloc = std::make_shared<Alloc>();
rclcpp::PublisherOptionsWithAllocator<Alloc> publisher_options;
publisher_options.allocator = alloc;
auto publisher = node->create_publisher<std_msgs::msg::UInt32>(
"allocator_example", 10, publisher_options);
rclcpp::SubscriptionOptionsWithAllocator<Alloc> subscription_options;
subscription_options.allocator = alloc;
auto msg_mem_strat = std::make_shared<
rclcpp::message_memory_strategy::MessageMemoryStrategy<
std_msgs::msg::UInt32, Alloc>>(alloc);
auto subscriber = node->create_subscription<std_msgs::msg::UInt32>(
"allocator_example", 10, callback, subscription_options, msg_mem_strat);
// 创建一个MemoryStrategy,它处理执行路径中由Executor进行的分配,
// 并将MemoryStrategy注入Executor。
std::shared_ptr<rclcpp::memory_strategy::MemoryStrategy> memory_strategy =
std::make_shared<AllocatorMemoryStrategy<Alloc>>(alloc);
rclcpp::ExecutorOptions options;
options.memory_strategy = memory_strategy;
rclcpp::executors::SingleThreadedExecutor executor(options);
// 将我们的节点添加到执行器中。
executor.add_node(node);
// 使用命名空间简化代码
using MessageAllocTraits =
rclcpp::allocator::AllocRebind<std_msgs::msg::UInt32, Alloc>;
using MessageAlloc = MessageAllocTraits::allocator_type;
using MessageDeleter = rclcpp::allocator::Deleter<MessageAlloc, std_msgs::msg::UInt32>;
using MessageUniquePtr = std::unique_ptr<std_msgs::msg::UInt32, MessageDeleter>;
MessageDeleter message_deleter; // 定义消息删除器对象
MessageAlloc message_alloc = *alloc; // 从共享指针分配器中获取消息分配器
rclcpp::allocator::set_allocator_for_deleter(&message_deleter, &message_alloc); // 设置删除器的分配器
rclcpp::sleep_for(std::chrono::milliseconds(1)); // 休眠1毫秒
uint32_t i = 0; // 定义并初始化计数器
while (rclcpp::ok() && i < 100) { // 当ROS 2系统正常运行且计数器小于100时循环
// 使用自定义分配器分配消息内存
auto ptr = MessageAllocTraits::allocate(message_alloc, 1);
// 构造消息对象
MessageAllocTraits::construct(message_alloc, ptr);
// 创建唯一指针消息对象,使用自定义删除器
MessageUniquePtr msg(ptr, message_deleter);
msg->data = i; // 设置消息数据
++i; // 增加计数器
publisher->publish(std::move(msg)); // 发布消息
rclcpp::sleep_for(std::chrono::milliseconds(1)); // 休眠1毫秒
executor.spin_some(); // 执行一些回调
}
return 0; // 返回0,表示程序正常结束
}
附录:
https://github.com/ros2/demos/blob/jazzy/demo_nodes_cpp/src/topics/allocator_tutorial_pmr.cpp
使用自定义内存分配器的ROS 2示例程序。该程序演示了如何创建自定义内存资源,并将其用于ROS 2节点的发布者和订阅者。程序还重写了全局的new
和delete
操作符,以计算在程序执行期间的内存分配和释放次数。
#include <chrono> // 包含用于处理时间的头文件
#include <list> // 包含用于处理列表的头文件
#include <memory> // 包含用于处理内存的头文件
#include <memory_resource> // 包含用于处理内存资源的头文件
#include <stdexcept> // 包含用于处理异常的头文件
#include <string> // 包含用于处理字符串的头文件
#include <utility> // 包含用于处理实用工具的头文件
#include "rclcpp/rclcpp.hpp" // 包含ROS 2的C++客户端库
#include "rclcpp/allocator/allocator_common.hpp" // 包含ROS 2的分配器通用头文件
#include "rclcpp/strategies/allocator_memory_strategy.hpp" // 包含ROS 2的分配器内存策略头文件
#include "std_msgs/msg/u_int32.hpp" // 包含标准消息类型UInt32的头文件
using namespace std::chrono_literals; // 使用chrono命名空间中的字面量
// 仅用于演示目的,不是allocator_traits所必需的
static uint32_t num_allocs = 0; // 记录分配次数
static uint32_t num_deallocs = 0; // 记录释放次数
// 一个非常简单的自定义内存资源。计算do_allocate和do_deallocate的调用次数。
class CustomMemoryResource : public std::pmr::memory_resource
{
private:
// 重写do_allocate方法,用于分配内存
void * do_allocate(std::size_t bytes, std::size_t alignment) override
{
num_allocs++; // 增加分配次数
(void)alignment; // 忽略对齐参数
return std::malloc(bytes); // 使用malloc分配内存
}
// 重写do_deallocate方法,用于释放内存
void do_deallocate(
void * p, std::size_t bytes,
std::size_t alignment) override
{
num_deallocs++; // 增加释放次数
(void)bytes; // 忽略字节数参数
(void)alignment; // 忽略对齐参数
std::free(p); // 使用free释放内存
}
// 重写do_is_equal方法,用于比较内存资源是否相等
bool do_is_equal(
const std::pmr::memory_resource & other) const noexcept override
{
return this == &other; // 比较内存资源是否相等
}
};
// 重写全局new和delete以在执行期间计算调用次数。
static bool is_running = false; // 标记程序是否正在运行
static uint32_t global_runtime_allocs = 0; // 记录全局分配次数
static uint32_t global_runtime_deallocs = 0; // 记录全局释放次数
// 由于GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103993,我们
// 始终内联重写的new和delete操作符。
#if defined(__GNUC__) || defined(__clang__)
#define NOINLINE __attribute__((noinline)) // 定义NOINLINE宏,用于不内联函数
#else
#define NOINLINE
#endif
NOINLINE void * operator new(std::size_t size)
{
if (size == 0) {
++size; // 如果大小为0,则将其增加1
}
if (is_running) {
global_runtime_allocs++; // 增加全局分配次数
}
void * ptr = std::malloc(size); // 使用malloc分配内存
if (ptr != nullptr) {
return ptr; // 如果分配成功,则返回指针
}
throw std::bad_alloc{}; // 如果分配失败,则抛出bad_alloc异常
}
NOINLINE void operator delete(void * ptr, size_t size) noexcept
{
(void)size; // 忽略大小参数
if (ptr != nullptr) {
if (is_running) {
global_runtime_deallocs++; // 增加全局释放次数
}
std::free(ptr); // 使用free释放内存
}
}
NOINLINE void operator delete(void * ptr) noexcept
{
if (ptr != nullptr) {
if (is_running) {
global_runtime_deallocs++; // 增加全局释放次数
}
std::free(ptr); // 使用free释放内存
}
}
int main(int argc, char ** argv)
{
using rclcpp::memory_strategies::allocator_memory_strategy::AllocatorMemoryStrategy;
using Alloc = std::pmr::polymorphic_allocator<void>;
using MessageAllocTraits =
rclcpp::allocator::AllocRebind<std_msgs::msg::UInt32, Alloc>;
using MessageAlloc = MessageAllocTraits::allocator_type;
using MessageDeleter = rclcpp::allocator::Deleter<MessageAlloc, std_msgs::msg::UInt32>;
using MessageUniquePtr = std::unique_ptr<std_msgs::msg::UInt32, MessageDeleter>;
rclcpp::init(argc, argv); // 初始化ROS 2
rclcpp::Node::SharedPtr node; // 定义共享指针节点
std::list<std::string> keys = {"intra", "intraprocess", "intra-process", "intra_process"}; // 定义命令行参数列表
bool intra_process = false; // 标记是否使用进程内通信
printf(
"This simple demo shows off a custom memory allocator to count all\n"
"instances of new/delete in the program. It can be run in either regular\n"
"mode (no arguments), or in intra-process mode (by passing 'intra' as a\n"
"command-line argument). It will then publish a message to the\n"
"'/allocator_tutorial' topic every 10 milliseconds until Ctrl-C is pressed.\n"
"At that time it will print a count of the number of allocations and\n"
"deallocations that happened during the program.\n\n"); // 打印程序说明
if (argc > 1) {
for (auto & key : keys) {
if (std::string(argv[1]) == key) {
intra_process = true; // 如果命令行参数匹配,则启用进程内通信
break;
}
}
}
if (intra_process) {
printf("Intra-process pipeline is ON.\n"); // 打印进程内通信开启
auto context = rclcpp::contexts::get_global_default_context();
auto options = rclcpp::NodeOptions()
.context(context)
.use_intra_process_comms(true); // 设置进程内通信选项
node = rclcpp::Node::make_shared("allocator_tutorial", options); // 创建节点
} else {
auto options = rclcpp::NodeOptions()
.use_intra_process_comms(false); // 设置非进程内通信选项
printf("Intra-process pipeline is OFF.\n"); // 打印进程内通信关闭
node = rclcpp::Node::make_shared("allocator_tutorial", options); // 创建节点
}
uint32_t counter = 0; // 定义计数器
auto callback = &counter -> void
{
(void)msg; // 忽略消息参数
++counter; // 增加计数器
};
// 创建自定义分配器并将其传递给发布者和订阅者。
CustomMemoryResource mem_resource{};
auto alloc = std::make_shared<Alloc>(&mem_resource);
rclcpp::PublisherOptionsWithAllocator<Alloc> publisher_options;
publisher_options.allocator = alloc;
auto publisher = node->create_publisher<std_msgs::msg::UInt32>(
"allocator_tutorial", 10, publisher_options); // 创建发布者
rclcpp::SubscriptionOptionsWithAllocator<Alloc> subscription_options;
subscription_options.allocator = alloc;
auto msg_mem_strat = std::make_shared<
rclcpp::message_memory_strategy::MessageMemoryStrategy<
std_msgs::msg::UInt32, Alloc>>(alloc);
auto subscriber = node->create_subscription<std_msgs::msg::UInt32>(
"allocator_tutorial", 10, callback, subscription_options, msg_mem_strat); // 创建订阅者
// 创建MemoryStrategy,它处理执行路径中由Executor进行的分配,并将MemoryStrategy注入Executor。
std::shared_ptr<rclcpp::memory_strategy::MemoryStrategy> memory_strategy =
std::make_shared<AllocatorMemoryStrategy<Alloc>>(alloc);
rclcpp::ExecutorOptions options;
options.memory_strategy = memory_strategy;
rclcpp::executors::SingleThreadedExecutor executor(options); // 创建单线程执行器
// 将节点添加到执行器中。
executor.add_node(node);
MessageDeleter message_deleter; // 定义消息删除器
MessageAlloc message_alloc = *alloc; // 定义消息分配器
rclcpp::allocator::set_allocator_for_deleter(&message_deleter, &message_alloc); // 设置消息删除器的分配器
rclcpp::sleep_for(1ms); // 休眠1毫秒
is_running = true; // 标记程序正在运行
uint32_t i = 0; // 定义计数器
while (rclcpp::ok()) {
// 使用自定义分配器创建消息,以便在执行路径中Executor释放消息时使用自定义释放。
auto ptr = MessageAllocTraits::allocate(message_alloc, 1);
MessageAllocTraits::construct(message_alloc, ptr);
MessageUniquePtr msg(ptr, message_deleter);
msg->data = i;
++i;
publisher->publish(std::move(msg)); // 发布消息
rclcpp::sleep_for(10ms); // 休眠10毫秒
executor.spin_some(); // 执行一些回调
}
is_running = false; // 标记程序停止运行
uint32_t final_global_allocs = global_runtime_allocs; // 获取最终全局分配次数
uint32_t final_global_deallocs = global_runtime_deallocs; // 获取最终全局释放次数
printf("Global new was called %u times during spin\n", final_global_allocs); // 打印全局分配次数
printf("Global delete was called %u times during spin\n", final_global_deallocs); // 打印全局释放次数
printf("Allocator new was called %u times during spin\n", num_allocs); // 打印分配器分配次数
printf("Allocator delete was called %u times during spin\n", num_deallocs); // 打印分配器释放次数
return 0; // 返回0,表示程序正常结束
}