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内存:
注意:必须确保内存生命周期与ROS消息绑定,避免野指针。// 用自定义删除器包装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());
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
相关产品推荐
相关产品推荐

