You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UE5向ROS发布图像时Memcpy性能极差的优化咨询

问题描述

环境配置:

  • 系统:Ubuntu 20
  • Unreal版本:5.2
  • 硬件:Ryzen 5 5600X、RTX 3070、40GB内存

当前实现的图像发布代码(C++):

void FROSOutputServer::PublishImage(ros::Publisher& ImagePublisher, TSharedPtr<FSensorDataBase>& SensorData){ 
   
    TRACE_CPUPROFILER_EVENT_SCOPE(FROSOuputServer::PublishImage);
    FCameraData* CameraData = static_cast<FCameraData*>(SensorData.Get());
    
    sensor_msgs::ImagePtr ImgMsgPtr = boost::make_shared<sensor_msgs::Image>();

    ros::Time TimeStamp;
    TimeStamp.fromSec(CameraData->Timestamp.ToDouble());
    ImgMsgPtr->header.stamp = TimeStamp;
    ImgMsgPtr->header.frame_id = "test";
    ImgMsgPtr->step = CameraData->Width * 3; 
    ImgMsgPtr->height = CameraData->Height;
    ImgMsgPtr->width = CameraData->Width;
    ImgMsgPtr->encoding = "bgr8";
    ImgMsgPtr->is_bigendian = 0;
    
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(FROSOuputServer::PublishImage::Copy);
        ImgMsgPtr->data.resize(CameraData->ImageData.Num());
        uint8* DestPtr = ImgMsgPtr->data.data();
        uint8* SrcPtr = CameraData->ImageData.GetData();
        FMemory::Memcpy(DestPtr, SrcPtr, CameraData->ImageData.Num());
    }
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(FROSOuputServer::PublishImage::Publish);
        ImagePublisher.publish(ImgMsgPtr);
    }

    // Only for debugging purposes, this would be called implicitly by the shared pointer destructor
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(FROSOuputServer::PublishImage::FreeUnrealPtr);
        SensorData.Reset();
    }

    {
        TRACE_CPUPROFILER_EVENT_SCOPE(FROSOuputServer::PublishImage::FreeBoostPtr);
        ImgMsgPtr.reset();
    }
}

对应的Unreal数据结构体:

struct FSensorDataBase
{
    Utils::Time Timestamp;
};
struct FCameraData : public FSensorDataBase
{
    
    TArray<uint8> ImageData;
    uint32 Width;
    uint32 Height;
};

测试场景:使用1000x1000的图像(对应3MB的TArray),通过Unreal Insights分析发现性能问题:

  • 图像数据复制操作耗时约13.5ms
  • 释放boost共享指针耗时约20.8ms(Unreal共享指针未在此作用域释放)

需优化方案,或确认是否受限于Unreal的CPU开销。

优化方案

1. 零拷贝消除数据复制开销

ROS的sensor_msgs::Image支持共享内存机制避免全量数据拷贝,核心思路是让ROS消息直接接管Unreal图像数据的内存:

  • 统一内存分配器
    修改FCameraData的ImageData使用Boost兼容的内存分配器,构造ROS消息时直接指向该内存区域,无需Memcpy:
    // 调整FCameraData的ImageData定义
    TArray<uint8, TAllocatorWrapper<boost::allocator<uint8>>> ImageData;
    
    // 构造ROS消息时直接赋值
    ImgMsgPtr->data = std::vector<uint8>(CameraData->ImageData.GetData(), CameraData->ImageData.GetData() + CameraData->ImageData.Num());
    
  • 自定义删除器共享内存
    若保留Unreal原生TArray,可通过boost::shared_ptr的自定义删除器让ROS消息管理TArray内存:
    // 用自定义删除器包装TArray的内存,绑定SensorData生命周期
    auto deleter = [sensor_data = std::move(SensorData)](uint8*) {};
    boost::shared_ptr<uint8> shared_buffer(CameraData->ImageData.GetData(), deleter);
    
    // 直接将ROS消息的data指向共享内存
    ImgMsgPtr->data.assign(shared_buffer.get(), shared_buffer.get() + CameraData->ImageData.Num());
    
    注意:必须确保内存生命周期与ROS消息绑定,避免野指针。

2. 减少内存分配/释放开销

  • 复用消息对象
    维护一个sensor_msgs::ImagePtr对象池(如std::queue<sensor_msgs::ImagePtr>),需要时取出重置字段和数据,使用完放回池内,大幅减少内存分配和销毁的开销。
  • 提前初始化消息容量
    在构造ROS消息时,直接初始化data的容量为图像大小,避免resize操作带来的内存重新分配:
    ImgMsgPtr->data.reserve(CameraData->ImageData.Num());
    

3. 优化Unreal内存模型适配

  • 使用ExternalData管理TArray内存
    利用Unreal的TArray::SetExternalData接口,让ImageData使用系统原生内存分配器,避免Unreal内部分配器与Boost/ROS之间的跨模块内存管理开销。
  • 复用FCameraData对象
    避免频繁创建和销毁FCameraData实例,维护一个对象池,重复使用图像数据容器,减少Unreal垃圾回收(GC)的CPU占用。

4. 异步解耦发布流程

将图像数据的ROS转换与发布操作转移到独立线程,避免阻塞Unreal的主线程或渲染线程:

  • 创建线程安全的任务队列(如FThreadSafeQueue<TSharedPtr<FCameraData>>),将图像数据推入队列;
  • 后台线程循环从队列取数据,转换为ROS消息并发布;
  • 用互斥锁或原子变量保证队列的线程安全访问。

5. 排查Unreal端隐性开销

  • 用Unreal Insights查看内存拷贝时的缓存命中率,若CameraData->ImageData刚从GPU拷贝过来,可能存在CPU缓存未命中,可尝试将图像数据预加载到CPU缓存;
  • 检查是否有Unreal引擎的后台任务(如GC、资产加载)与当前操作抢占CPU资源,可调整引擎的GC频率或任务优先级。

内容的提问来源于stack exchange,提问作者Larsinator9000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:44:53