ROS2入门教程-实现自定义内存分配器
说明:
- 介绍如何为发布者和订阅者集成自定义分配器,以便在ROS节点执行时永远不会调用默认堆分配器
步骤:
默认情况下,许多 C++ 标准库结构会随着它们的增长而隐式分配内存,例如std::vector. 但是,这些数据结构也接受“分配器”模板参数。如果您为这些数据结构之一指定自定义分配器,它将为您使用该分配器而不是系统分配器来增大或缩小数据结构。您的自定义分配器可以在堆栈上预先分配一个内存池,这可能更适合实时应用程序
要编写与 ROS 2 的分配器接口兼容的分配器,您的分配器必须与 C++ 标准库分配器接口兼容。
C++11 库提供了一个叫做allocator_traits. C++11 标准规定,自定义分配器只需要满足用于以标准方式分配和释放内存的最低要求集。allocator_traits是一种通用结构,它根据以最低要求编写的分配器来填充分配器的其他特性
自定义分配器的以下声明将满足allocator_traits
template <class T>
struct custom_allocator {
using value_type = T;
custom_allocator() noexcept;
template <class U> custom_allocator (const custom_allocator<U>&) noexcept;
T* allocate (std::size_t n);
void deallocate (T* p, std::size_t n);
};
template <class T, class U>
constexpr bool operator== (const custom_allocator<T>&, const custom_allocator<U>&) noexcept;
template <class T, class U>
constexpr bool operator!= (const custom_allocator<T>&, const custom_allocator<U>&) noexcept;
- 使用具有部分 C++11 支持的编译器,分配器如下
template<typename T>
struct pointer_traits {
using reference = T &;
using const_reference = const T &;
};
// Avoid declaring a reference to void with an empty specialization
template<>
struct pointer_traits<void> {
};
template<typename T = void>
struct MyAllocator : public pointer_traits<T> {
public:
using value_type = T;
using size_type = std::size_t;
using pointer = T *;
using const_pointer = const T *;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
MyAllocator() noexcept;
~MyAllocator() noexcept;
template<typename U>
MyAllocator(const MyAllocator<U> &) noexcept;
T * allocate(size_t size, const void * = 0);
void deallocate(T * ptr, size_t size);
template<typename U>
struct rebind {
typedef MyAllocator<U> other;
};
};
template<typename T, typename U>
constexpr bool operator==(const MyAllocator<T> &,
const MyAllocator<U> &) noexcept;
template<typename T, typename U>
constexpr bool operator!=(const MyAllocator<T> &,
const MyAllocator<U> &) noexcept;
- 一旦编写了有效的 C++ 分配器,就必须将它作为共享指针传递给发布者、订阅者和执行者
auto alloc = std::make_shared<MyAllocator<void>>();
auto publisher = node->create_publisher<std_msgs::msg::UInt32>("allocator_example", 10, alloc);
auto msg_mem_strat =
std::make_shared<rclcpp::message_memory_strategy::MessageMemoryStrategy<std_msgs::msg::UInt32,
MyAllocator<>>>(alloc);
auto subscriber = node->create_subscription<std_msgs::msg::UInt32>(
"allocator_example", 10, callback, nullptr, false, msg_mem_strat, alloc);
std::shared_ptr<rclcpp::memory_strategy::MemoryStrategy> memory_strategy =
std::make_shared<AllocatorMemoryStrategy<MyAllocator<>>>(alloc);
rclcpp::executors::SingleThreadedExecutor executor(memory_strategy);
- 实例化节点并将执行程序添加到节点后,就可以旋转
uint32_t i = 0;
while (rclcpp::ok()) {
msg->data = i;
i++;
publisher->publish(msg);
rclcpp::utilities::sleep_for(std::chrono::milliseconds(1));
executor.spin_some();
}
- IntraProcessManager 是一个通常对用户隐藏的类,但为了将自定义分配器传递给它,我们需要通过从 rclcpp 上下文中获取它来公开它。IntraProcessManager 使用多个标准库结构,因此如果没有自定义分配器,它将调用默认的 new
auto context = rclcpp::contexts::default_context::get_global_default_context();
auto ipm_state =
std::make_shared<rclcpp::intra_process_manager::IntraProcessManagerState<MyAllocator<>>>();
// Constructs the intra-process manager with a custom allocator.
context->get_sub_context<rclcpp::intra_process_manager::IntraProcessManager>(ipm_state);
auto node = rclcpp::Node::make_shared("allocator_example", true);
- 测试和验证代码,计算对自定义分配器allocate和deallocate函数的调用,并将其与对new和的调用进行比较delete。向自定义分配器添加计数
T * allocate(size_t size, const void * = 0) {
// ...
num_allocs++;
// ...
}
void deallocate(T * ptr, size_t size) {
// ...
num_deallocs++;
// ...
}
- 覆盖全局 new 和 delete 运算符
void operator delete(void * ptr) noexcept {
if (ptr != nullptr) {
if (is_running) {
global_runtime_deallocs++;
}
std::free(ptr);
ptr = nullptr;
}
}
void operator delete(void * ptr, size_t) noexcept {
if (ptr != nullptr) {
if (is_running) {
global_runtime_deallocs++;
}
std::free(ptr);
ptr = nullptr;
}
}
- 打印变量的值。要运行示例可执行文件
allocator_example
- 使用进程内管道运行示例
allocator_example intra-process
- 结果如下
Global new was called 15590 times during spin
Global delete was called 15590 times during spin
Allocator new was called 27284 times during spin
Allocator delete was called 27281 times during spin
获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号