Нулевое копирование в ROS2 обеспечивает эффективную связь между узлами внутри процесса.
Нулевое копирование в ROS2 обеспечивает эффективную связь между узлами внутри процесса.

фон

Приложения ROS обычно состоят из отдельных «узлов», которые выполняют одну задачу и отделены от остальной системы. Это способствует изоляции ошибок, ускорению разработки, модульности и повторному использованию кода, но часто за счет производительности. После первоначальной разработки ROS1 необходимость в эффективной комбинации узлов стала очевидной, поэтому были разработаны Nodelets. В ROS2 целью является улучшение конструкции узлов путем решения некоторых основных проблем, требующих реконструкции узлов.

Основное содержание статьи

  • Представляем реализацию внутрипроцессной (intra_process) публикации и подписки тем под ros2.
  • Разные узлы в одном процессе могут читать содержимое через общие указатели, чтобы уменьшить накладные расходы на копирование сообщений. Intra_process не может добиться нулевого копирования для узлов между разными процессами. Способ распространения сообщений определяется в зависимости от типа обратного вызова подписки. Копирование «один к одному» будет нулевым, а копирование «один ко многим» будет автоматически копировать n-1 сообщение.
  • Эффективность и производительность обработки между узлами с относительно большими объемами данных, например изображений, будут значительно повышены.

В этой демонстрации мы сосредоточимся на том, как вручную объединять узлы.,Метод заключается в том, чтобы определить узлы отдельно.,но комбинируйте их в разных схемах процессов,без изменения кода узла и ограничения его функциональности。Отображается при использовании Когда std::unique_ptr публикует и подписывается, он реализует внутрипроцессные соединения публикации/подписки и может обеспечить передачу сообщений без копирования.

Для начала давайте взглянем на исходный код:

Язык кода:javascript
копировать
#include <chrono>
#include <cinttypes>
#include <cstdio>
#include <memory>
#include <string>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
using namespace std::chrono_literals;
// Node that produces messages.
struct Producer : public rclcpp::Node
{
  Producer(const std::string & name, const std::string & output)
  : Node(name, rclcpp::NodeOptions().use_intra_process_comms(true))
  {
    // Create a publisher on the output topic.
    pub_ = this->create_publisher<std_msgs::msg::Int32>(output, 10);
    std::weak_ptr<std::remove_pointer<decltype(pub_.get())>::type> captured_pub = pub_;
    // Create a timer which publishes on the output topic at ~1Hz.
    auto callback = [captured_pub]() -> void {
        auto pub_ptr = captured_pub.lock();
        if (!pub_ptr) {
          return;
        }
        static int32_t count = 0;
        std_msgs::msg::Int32::UniquePtr msg(new std_msgs::msg::Int32());
        msg->data = count++;
        printf(
          "Published message with value: %d, and address: 0x%" PRIXPTR "\n", msg->data,
          reinterpret_cast<std::uintptr_t>(msg.get()));
        pub_ptr->publish(std::move(msg));
      };
    timer_ = this->create_wall_timer(1s, callback);
  }

  rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pub_;
  rclcpp::TimerBase::SharedPtr timer_;
};
// Node that consumes messages.
struct Consumer : public rclcpp::Node
{
  Consumer(const std::string & name, const std::string & input)
  : Node(name, rclcpp::NodeOptions().use_intra_process_comms(true))
  {
    // Create a subscription on the input topic which prints on receipt of new messages.
    sub_ = this->create_subscription<std_msgs::msg::Int32>(
      input,
      10,
      [](std_msgs::msg::Int32::UniquePtr msg) {
        printf(
          " Received message with value: %d, and address: 0x%" PRIXPTR "\n", msg->data,
          reinterpret_cast<std::uintptr_t>(msg.get()));
      });
  }

  rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr sub_;
};

int main(int argc, char * argv[])
{
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);
  rclcpp::init(argc, argv);
  rclcpp::executors::SingleThreadedExecutor executor;

  auto producer = std::make_shared<Producer>("producer", "number");
  auto consumer = std::make_shared<Consumer>("consumer", "number");
  executor.add_node(producer);
  executor.add_node(consumer);
  executor.spin();
  rclcpp::shutdown();
  return 0;
}

Вы можете видеть, что Producer наследует от Node и устанавливает для use_intra_process_comms значение true, чтобы можно было использовать механизм внутри_процесса. Вы можете видеть, что Producer наследует от Node и устанавливает для use_intra_process_comms значение true, чтобы можно было использовать механизм внутри_процесса. Как вы можете видеть, взглянув на функцию main, у нас есть узел-производитель и потребитель, добавьте их в однопоточный исполнитель, а затем вызовите spin. Глядя на реализацию узла «производитель» в структуре производителя, вы можете видеть, что мы создали публикатора, который публикует «числовую» тему, и таймер, который регулярно создает новые сообщения, распечатывает адрес в памяти и значение его содержимого. и публикует его. Узел «потребитель» немного проще, и его реализацию можно увидеть в структуре потребителя, поскольку он подписывается только на тему «номер» и печатает адрес и значение получаемых сообщений.

Производитель распечатывает адрес и соответствующее значение, как и ожидалось, а потребитель распечатывает соответствующий адрес и соответствующее значение. Это показывает, что внутрипроцессное взаимодействие действительно работает и позволяет избежать ненужного копирования, по крайней мере, для простых изображений.

При выполнении исполняемого файла ros2 runtra_process_demo two_node_pipeline результаты печати будут следующими:

Сообщения отправляются со скоростью примерно одно в секунду. Это потому, что мы сказали таймеру срабатывать примерно раз в секунду. Кроме того, мы замечаем, что первое сообщение (со значением 0) не имеет соответствующей строки «Полученное сообщение...». Это связано с тем, что публикация/подписка является «максимальной мерой» и не допускает какого-либо поведения, подобного «блокировке». Это означает, что если издатель опубликует сообщение до того, как будет установлена ​​подписка, подписка не получит это сообщение. Это состояние гонки может привести к потере первых нескольких сообщений. В этом случае, поскольку они происходят только один раз в секунду, обычно теряется только первое сообщение. Наконец, вы можете видеть, что строки «Опубликованное сообщение...» и «Полученное сообщение...» с одинаковым значением также имеют один и тот же адрес. Это указывает на то, что полученный адрес сообщения совпадает с опубликованным адресом сообщения и не является копией.

Это потому, что мы публикуем и подписываемся на std::unique_ptrs.,Это позволяет владельцам сообщений безопасно перемещаться внутри системы.。Конечно, вы также можете использоватьconst&иstd::shared_ptr发布и订阅,Но нулевой копии в этом случае нет.

Вот объяснение использования и разницы между std::unique_ptr и std::shared_ptr.

Роль интеллектуальных указателей такова: Интеллектуальные указатели используются для обеспечения возможности автоматического удаления объектов, когда они больше не используются.

std::unique_ptr :одинstd::unique_ptr会指向один对象且不允许其他указатель指向。创建одинuniqueуказатель,Поместить в контейнер (например, карту),Но возвращает фактический указатель,Назначьте другому объекту.полю. Объект, на который указывает std::unique_ptr, можно переместить в другой указатель.,Когда заостренный предмет умирает,Контейнер автоматически освободит все ресурсы. поэтому,std::unique_ptr играет роль в управлении ресурсами памяти.,Настоящие указатели можно использовать везде.

std::shared_ptr:Сам жизненный цикл относительно фиксирован,std::shared_ptr разделяет указатель между несколькими объектами,Жизненный цикл этих объектов относительно динамичен.,Указатели освобождаются, когда все объекты завершены.

Таким образом, std::shared_ptr — это указатель на разделяемую память, а std::unique_ptr — это указатель, которому не нужно управлять памятью самостоятельно.

Цикл нулевой копии

Эта демонстрация похожа на предыдущую, но вместо того, чтобы производитель создавал новое сообщение для каждой итерации, в этой демонстрации используется только один экземпляр сообщения. Это достигается путем создания цикла и «запуска» связи через внешний узел перед тем, как исполнитель функции обратного вызова опубликует сообщение:

Язык кода:javascript
копировать
#include <chrono>
#include <cinttypes>
#include <cstdio>
#include <memory>
#include <string>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"

using namespace std::chrono_literals;
// This node receives an Int32, waits 1 second, then increments and sends it.
struct IncrementerPipe : public rclcpp::Node
{
  IncrementerPipe(const std::string & name, const std::string & in, const std::string & out)
  : Node(name, rclcpp::NodeOptions().use_intra_process_comms(true))
  {
    // Create a publisher on the output topic.
    pub = this->create_publisher<std_msgs::msg::Int32>(out, 10);
    std::weak_ptr<std::remove_pointer<decltype(pub.get())>::type> captured_pub = pub;
    // Create a subscription on the input topic.
    sub = this->create_subscription<std_msgs::msg::Int32>(
      in,
      10,
      [captured_pub](std_msgs::msg::Int32::UniquePtr msg) {
        auto pub_ptr = captured_pub.lock();
        if (!pub_ptr) {
          return;
        }
        printf(
          "Received message with value:         %d, and address: 0x%" PRIXPTR "\n", msg->data,
          reinterpret_cast<std::uintptr_t>(msg.get()));
        printf("  sleeping for 1 second...\n");
        if (!rclcpp::sleep_for(1s)) {
          return;    // Return if the sleep failed (e.g. on ctrl-c).
        }
        printf("  done.\n");
        msg->data++;    // Increment the message's data.
        printf(
          "Incrementing and sending with value: %d, and address: 0x%" PRIXPTR "\n", msg->data,
          reinterpret_cast<std::uintptr_t>(msg.get()));
        pub_ptr->publish(std::move(msg));    // Send the message along to the output topic.
      });
  }
  rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pub;
  rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr sub;
};


int main(int argc, char * argv[])
{
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);
  rclcpp::init(argc, argv);
  rclcpp::executors::SingleThreadedExecutor executor;
  // Create a simple loop by connecting the in and out topics of two IncrementerPipe's.
  // The expectation is that the address of the message being passed between them never changes.
  auto pipe1 = std::make_shared<IncrementerPipe>("pipe1", "topic1", "topic2");
  auto pipe2 = std::make_shared<IncrementerPipe>("pipe2", "topic2", "topic1");
  rclcpp::sleep_for(1s);  // Wait for subscriptions to be established to avoid race conditions.
  // Publish the first message (kicking off the cycle).
  std::unique_ptr<std_msgs::msg::Int32> msg(new std_msgs::msg::Int32());
  msg->data = 42;
  printf(
    "Published first message with value:  %d, and address: 0x%" PRIXPTR "\n", msg->data,
    reinterpret_cast<std::uintptr_t>(msg.get()));
  pipe1->pub->publish(std::move(msg));
  executor.add_node(pipe1);
  executor.add_node(pipe2);
  executor.spin();
  rclcpp::shutdown();
  return 0;
}

Отличие от предыдущего примера в том, что в этой демонстрации используется только один узел.,用不同的名称и配置实例化了两次。эта линияpipe1->pub->pub(msg);начать процесс,но с тех пор,Каждый узел вызывает публикацию в своей собственной функции обратного вызова подписки.,Сообщения передаются туда и обратно между узлами. Ожидается, что узел будет доставлять сообщения туда и обратно один раз в секунду.,Каждый раз увеличивает значение сообщения. Потому что сообщение опубликовано и подписано как unique_ptr.,Таким образом, то же самое сообщение, созданное вначале, будет использоваться и дальше. Результаты, распечатанные после запуска, следующие:

Язык кода:javascript
копировать
Published first message with value:  42, and address: 0x7fd2ce0a2bc0
Received message with value:         42, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
  done.
Incrementing and sending with value: 43, and address: 0x7fd2ce0a2bc0
Received message with value:         43, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
  done.
Incrementing and sending with value: 44, and address: 0x7fd2ce0a2bc0
Received message with value:         44, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
  done.
Incrementing and sending with value: 45, and address: 0x7fd2ce0a2bc0
Received message with value:         45, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
  done.
Incrementing and sending with value: 46, and address: 0x7fd2ce0a2bc0
Received message with value:         46, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
  done.
Incrementing and sending with value: 47, and address: 0x7fd2ce0a2bc0
Received message with value:         47, and address: 0x7fd2ce0a2bc0
  sleeping for 1 second...
[...]

Отсюда вы можете видеть, что число продолжает увеличиваться на каждой итерации, начиная с 42... и на протяжении всего процесса оно повторно использует одно и то же сообщение, а адрес его указателя никогда не меняется, что позволяет избежать ненужных копий.

Поэтому, когда нам нужно передать большое количество изображений или данных облака точек в нашем проекте, мы можем использовать этот метод для достижения эффективной связи между процессами. Далее мы реализуем демонстрацию передачи изображений opencv с использованием OpenCV Capture images и аннотирования изображений. и просматривать изображения.

Сначала будет конвейер, состоящий из трёх узлов,Как показано ниже:camera_node->watermark_node->image_view_node

camera_node считывает необработанное изображение с номера устройства камеры на компьютере, записывает некоторую информацию об изображении и публикует ее. Watermark_node подписывается на выходные данные camera_node и добавляет к изображению некоторую текстовую информацию перед публикацией. Наконец, image_view_node подписывается на выходные данные Watermark_node, записывает дополнительную текстовую информацию на изображение, а затем использует cv::imshow для его визуализации.

В каждом узле адрес отправляемого или полученного сообщения записывается в изображение. Узлы информации о водяных знаках и визуализации изображения предназначены для изменения изображения без копирования изображения. Поэтому, пока узлы находятся в одном и том же месте. процесс, а изображение остается в процессе, как описано выше, и адрес, напечатанный на изображении, должен быть тот же. Ходовой узел такой, как показано на рисунке

Здесь вы можете приостановить рендеринг изображения, нажав клавишу пробела, а затем снова нажав клавишу пробела, чтобы продолжить рендеринг. Вы также можете нажать q или ESC для выхода. Если вы приостановите просмотр изображений, вы сможете сравнить адреса, написанные на изображении, и проверить, совпадают ли они.

Визуализация процесса с помощью двух изображений

В этом примере есть два узла визуализации изображений, все еще находящиеся в одном процессе, но теперь должны отображаться два окна визуализации изображений.

Как и в предыдущем примере, рендеринг можно приостановить с помощью клавиши пробела, а затем продолжить, снова нажав клавишу пробела. При этом проверка разрешения на обновление прекращается, чтобы увидеть, изменился ли указатель на экране. Как вы можете видеть на примере изображения выше, у нас есть одно изображение со всеми одинаковыми указателями, а затем другое изображение с теми же указателями, что и первое изображение, но последний указатель на втором изображении отличается. Чтобы понять, почему это происходит, подумайте о топологии графа:

camera_node -> watermark_node -> image_view_node-> image_view_node2

Ссылка между camera_node и Watermark_node может использовать один и тот же указатель без копирования, поскольку только одна подписка в процессе должна доставлять ему сообщения. Но для связи между Watermark_node и двумя узлами визуализации изображения отношение один ко многим, поэтому, если узел визуализации изображения использует обратный вызов unique_ptr, невозможно передать владение одним и тем же указателем обоим узлам. Однако его можно передать одному из них. Какой из них получит необработанный указатель, не определено, передается только последний.

Обратите внимание, что узел визуализации изображения не подписан на обратный вызов unique_ptr. Вместо этого используйте подписку const Shared_ptrs. Это означает, что система передает один и тот же общий_ptr в оба обратных вызова. Когда обрабатывается первая подписка в процессе, хранящийся внутри unique_ptr обновляется доshared_ptr. Каждый обратный вызов получит совместное владение одним и тем же сообщением.

Процесс с межпроцессной визуализацией

Еще одна важная вещь — избегать прерываний в процессе нулевого копирования при выполнении межпроцессных подписок. Чтобы проверить это, вы можете запустить первый пример конвейера изображений image_pipeline_all_in_one, а затем запустить автономный экземпляр image_view_node. Результат запуска следующий

Трудно приостановить два изображения одновременно, поэтому изображения могут не совпадать, но важно отметить, что представление изображения image_pipeline_all_in_one показывает один и тот же адрес для каждого шага. Это означает, что нулевая копия в процессе сохраняется, даже если подписано внешнее представление. Вы также можете видеть, что идентификаторы процессов для первых двух строк текста в представлении изображения между процессами и идентификаторы процессов для автономного средства просмотра изображений в третьей строке текста различаются.

Ссылки:

https://docs.ros.org/en/galactic/Tutorials/Demos/Intra-Process-Communication.html

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.