【ROS2】高级:实现自定义内存分配器

news2024/11/24 10:27:32

目标:本教程将展示在编写 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。该类重写了三个方法:

  1. do_allocate:用于分配指定大小和对齐方式的内存。

  2. do_deallocate:用于释放指定大小和对齐方式的内存。

  3. 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]

64ad9c5d75508311fc355c651af01efa.png

示例可执行文件打印变量的值。要运行示例可执行文件,请使用:

ros2 run demo_nodes_cpp allocator_tutorial

或者,使用内部进程管道运行示例:

ros2 run demo_nodes_cpp allocator_tutorial intra

你应该得到如下数字:

d4ca231a9aa8acc63fa099db8f109e71.png

我们已经捕捉到了执行路径上大约 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节点的发布者和订阅者。程序还重写了全局的newdelete操作符,以计算在程序执行期间的内存分配和释放次数。

#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,表示程序正常结束
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【个人笔记】685. 冗余连接 II 的解释(并查集)

一棵树有n个点和n条边&#xff0c;返回一条能删除的边&#xff0c;使得剩下的图是有 n 个节点的有根树。 解释&#xff1a; 注意不冗余的有根树的特性&#xff01;**根节点入度为0&#xff0c;其余结点只有一个入度&#xff01;**所以冗余的两种情况如下&#xff1a; &#xff…

jmeter持续学习之---控制器

IF控制器 下面这种写法jmeter不推荐有性能的问题 jmeter推荐勾选上的这种写法 使用"Interpret Condition as Variable Expression"工具的性能要好一些 循环控制器 ForEach控制器 与用户定义的变量或者正则表达式提取器配合使用,循环读取。用户定义的变量或者正则…

彻底改变时尚:使用 GAN 实现 AI 的未来

彻底改变时尚&#xff1a;使用 GAN 实现 AI 的未来 一、介绍 想象一下&#xff0c;在这个世界里&#xff0c;时装设计师永远不会用完新想法&#xff0c;我们穿的每一件衣服都是一件艺术品。听起来很有趣&#xff0c;对吧&#xff1f;好吧&#xff0c;我们可以在通用对抗网络 &a…

路网双线合并单线——ArcGISpro 解决方法

路网双线合并成单线是一个在地图制作、交通规划以及GIS分析中常见的需求。双线路网定义&#xff1a;具有不同流向、不同平面结构的道路。此外&#xff0c;车道数较多的道路&#xff08;例如&#xff0c;双黄实线车道数大于4的道路&#xff09;也可以视为双线路网&#xff0c;本…

C++相关概念和易错语法(22)(final、纯虚函数、继承多态难点)

1.final final在继承和多态中都可以使用&#xff0c;在继承中是指不想将自己被继承&#xff0c;在多态中是指不想该函数被重写&#xff0c;比较简单&#xff0c;下面是一些使用例子。 2.纯虚函数 当我们需要抽象一个类的时候&#xff0c;我们就需要用到纯虚函数。所谓抽象的类…

深入理解I/O模型

目录 一、I/O 模型简介 二、I/O 模型 2.1 同步阻塞 I/O 2.2 同步非阻塞I/O 2.3 I/O多路复用 2.4 异步I/O 2.5 信号驱动 I/O 三、总结 一、I/O 模型简介 所谓的 I/O 就是计算机内存与外部设备之间拷贝数据数据的过程。有 5 中 I/O 模型&#xff0c;分别是同步阻塞 I/O、同步…

单端、差分信号处理抗干扰能力解析

采用仪表运放对信号源进行处理&#xff0c; 信号源地上有共模干扰&#xff0c;经过差分信号处理后Vout上不会有干扰&#xff0c;差分信号可以非常好的抗共模干扰。 经过差分信号处理后&#xff0c;以单端信号输出进入ADC还是会有干扰&#xff0c;所以信号链采用差分 处理后&…

Java二十三种设计模式-适配器模式(6/23)

适配器模式&#xff1a;使不兼容的接口协同工作的桥梁 引言 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许不兼容的接口之间可以一起工作&#xff0c;通过将一个类的接口转换成客户端期望的另一个接口。 在计算机编程中&#x…

AI论文精读笔记-MAE

1. 论文基本信息 论文标题&#xff1a;Masked Autoencoders Are Scalable Vision Learners 作者&#xff1a;Kaiming He∗,† Xinlei Chen∗ Saining Xie Yanghao Li Piotr Doll ́ ar Ross Girshick 发表时间和期刊&#xff1a;19 Dec 2021; arxiv 论文链接&#xff1a;Mas…

苹果预计因Apple Intelligence引发设备升级潮

&#x1f989; AI新闻 &#x1f680; 苹果预计因Apple Intelligence引发设备升级潮 摘要&#xff1a;据彭博社报道&#xff0c;摩根士丹利将苹果列为首选股票&#xff0c;预期Apple Intelligence发布将引发创纪录的设备升级。分析师Erik Woodring 将苹果目标股价上调至273美元…

前端开发(基础)

目录 一、Web前端项目初始化 环境准备 创建项目 前端工程化配置 引入组件库 开发规范 全局通用布局 基础布局结构 全局底部栏 动态替换内容 全局顶部栏 通用路由菜单 支持多套布局 请求 请求工具库 全局自定义请求 自动生成请求代码 全局状态管理 全局权限管…

电力调度台如何助力电力指挥中心更智慧

在现代电力系统的复杂运行环境中&#xff0c;电力调度台正逐渐成为电力指挥中心实现智慧化管理的关键力量。 电力调度台具备强大的信息集成与处理能力。它能够将来自不同监测系统、传感器和数据源的海量数据汇聚一处&#xff0c;包括电力设备的实时运行状态、电力负荷的动态变化…

应急靶场(4):Windows Server 2019 - Web3

目录 一、攻击者的两个IP地址 二、隐藏用户名称 三、黑客遗留下的flag【3个】 下载好靶场&#xff08;前来挑战&#xff01;应急响应靶机训练-Web3&#xff09;并搭建好环境&#xff0c;使用帐号密码&#xff08;administrator / xj123456&#xff09;登录靶机。 一、攻击者的两…

张幼玲:心中有火,眼里有光照医路

在我们的传统社会中&#xff0c;男科医生这一职业往往被人们带着异样的眼光看待。然而&#xff0c;张幼玲却选择了这一领域&#xff0c;成为了一名专业男科医生。他以其丰富的临床经验、高超的医术和对患者的关爱&#xff0c;赢得了患者和社会的广泛赞誉。 张幼玲出生于一个中医…

ASP.NET Core----基础学习06----将所有数据在页面中显示 布局页面的使用

文章目录 1. 将数据以list的形式展示在页面中2. 布局页面的使用3. 自定义设置视图文件是否需要加载的JS 1. 将数据以list的形式展示在页面中 step1:在接口文件中添加新的方法GetAllStudents&#xff08;&#xff09; step2:在mock的数据中添加方法GetAllStudents&#xff08;&a…

7/13 - 7/15

vo.setId(rs.getLong("id"))什么意思&#xff1f; vo.setId(rs.getLong("id")); 这行代码是在Java中使用ResultSet对象&#xff08;通常用于从数据库中检索数据&#xff09;获取一个名为"id"的列&#xff0c;并将其作为long类型设置为一个对象…

Billu_b0x靶机

信息收集 使用arp-scan 生成网络接口地址来查看ip 输入命令&#xff1a; arp-scan -l 可以查看到我们的目标ip为192.168.187.153 nmap扫描端口开放 输入命令&#xff1a; nmap -min-rate 10000 -p- 192.168.187.153 可以看到开放2个端口 nmap扫描端口信息 输入命令&…

工作中项目git如何管理,冲突,push不上去如何解决

主要涉及的知识点 现在公司中一般的git仓库的管理方式是怎么样的代码为什么push不上线上仓库如何解决代码冲突 分支管理方式 git checkout -b 分支名字 是创建并切换到分支 git push origin 分支名字 推到远程仓库分支上 主流的git管理方式 共用一个仓库&#xff0c;不同…

Golang | Leetcode Golang题解之第237题删除链表中的节点

题目&#xff1a; 题解&#xff1a; func deleteNode(node *ListNode) {node.Val node.Next.Valnode.Next node.Next.Next }

解决宝塔Spring Boot项目获取不到环境变量的问题

问题描述 在使用宝塔面板管理Spring Boot项目时&#xff0c;可能会遇到代码无法获取 /etc/profile 文件中设置的Linux环境变量的问题。虽然在SSH终端中可以正常获取&#xff0c;但在通过宝塔面板启动的Spring Boot项目中&#xff0c;环境变量却无法被读取。 解决方案&#xf…