返回专辑
·Johan·5 分钟阅读

Eigen::Map:零拷贝映射外部缓冲

Eigen::Map 绑定外部 buffer 的对齐、lifetime 与 aliasing,适配 ROS/传感器零拷贝视图。

Eigen::Map:零拷贝映射外部缓冲

1. 驱动吐 buffer,Eigen 只想算——别顺手拷贝

Velodyne DMA 缓冲、ROS loaned message、共享内存点云——所有权在外部。Eigen::Map 把 plain array 包装成 Matrix/Vector 视图,写 Map 即写底层。省拷贝的前提是:layout、dtype、对齐、lease 四条契约你全掌控。错一条,轨迹可能仍收敛,残差统计却「怪」——更难查。集成测试若只用 synthetic 对齐数据,field 上 driver 真实 payload 仍会踩坑。

驱动层 DMA 缓冲、传感器 shared memory、ROS loaned message——内存所有权在外部,Eigen 只想做矩阵运算而不拷贝。Map 是零拷贝利器,前提是 layout 与 lifetime 你完全掌控。plugin 之间传 Map 类型会把 layout 细节焊进 ABI——更稳的是传 raw pointer + stride + dtype enum,在 consumer 内 Map。

2. layout 与 RowMajor

driver 按 interleaved xyz 排列时,Map 须 match storage order:

cpp
float* buf = driver.getPoints();
Eigen::Map<const Eigen::Matrix<float, 3, Eigen::Dynamic, Eigen::ColMajor>>
  pts(buf, 3, n);

错把 row-major buffer 当默认 ColMajor 读,是 silent 读错。ROS PointCloud2 须核对 point_stepfields[i].offset;livox 自定义 12B stride 时 intensity 单独 Map offset。ToF 驱动 interleaved RGBXYZ 排列时,RowMajor Dynamic×3 或手动 stride 二选一,文档写清。Velodyne 驱动吐出 packed float 数组,列数等于点数,storage order 要 match。

3. 生命周期:Map 不拥有

同步 callback 内 Map 可用;别 store Map 或 v.data() 到线程池。driver 下一帧 reuse buffer 时,上层仍 hold Map 即 UAF。pattern:copy 到 Eigen::Matrix,或延长 buffer pool lease 到 consumer 完成。

cpp
void on_cloud(float* data, size_t n) {
  Eigen::Map<Eigen::VectorXf> v(data, n);
  process(v);
}

异步 publish 时确保 message 在 callback 返回前有效,或 copy 出 Eigen 自有矩阵。Map 不拥有内存——buffer 释放后 Map 悬空。

4. 对齐与 Unaligned

EIGEN_MAKE_ALIGNED_OPERATOR_NEW 只影响 owning 类型;Map 对齐由 buffer 地址决定。UDP payload 常仅 4B align——用 Eigen::Map<Vector4f, Eigen::Unaligned> 或 copy 到 align buffer,否则 AVX 路径 SIGBUS。检查 reinterpret_cast<uintptr_t>(ptr) % 16 在 debug 入口做 assert。

cpp
Eigen::Map<const Eigen::Vector3f, Eigen::Unaligned>(
  reinterpret_cast<const float*>(pkt.data()));

uint16 depth 误 Map 成 float 会 silent scale 错——runtime dtype switch 在入口卡住。ROS message 的 float32 数组 Map 时 dtype 必须 match。

5. const Map 与 Eigen::Ref

只读算法传 Map<const Vector3d>,编译期禁止 accidental write。plugin 边界传 double* + stride + rows/cols,比 export Map 类型更少 ABI 耦合;内部再 Map。Eigen::Ref 绑 Map/Matrix 无 copy,适合函数参数泛化。配合 span<const byte> 收 raw lidar packet 再 Map,接口面更清楚。

6. 案例:PointCloud2 offset 漂移

某驱动升级后 intensity field offset 从 12 变 16,Map 仍按旧 offset 读——强度全零,下游 ground filter 误判。修法:unit test 用 synthetic msg 验证各 field;启动时 parse fields 表动态算 offset,不硬编码 magic number。PointCloud2 point_step 变更时 Map stride 同步改。这类 silent 错误在仿真短 bag 里可能不触发,field 长跑才暴露——driver 升级 checklist 须含 consumer Map 回归。

7. 验证

cpp
Eigen::Matrix3f m = Eigen::Matrix3f::Random();
float buf[9];
Eigen::Map<Eigen::Matrix3f>(buf) = m;
assert(std::equal(std::begin(buf), std::end(buf), m.data()));

synthetic msg unit test 验证 offset;async 路径 fuzz 多帧 reuse 无 UAF。row/col major 与 driver 文档一致;改 driver 版本时回归 residual 统计。

8. aliasing 与原地写

Map 绑定外部 buffer 后原地 +=、*= 可能与同一 buffer 其它视图 alias——Eigen 有时向量化失序。不确定时 .eval() 或 copy 到临时。写回 driver buffer 前确认 driver 允许 in-place 修改;loan 只读 message 上 Map 成 non-const 是契约破坏。单元测试:同一 buffer 双 Map 交叉写,对比 serial 参考结果。

10. 与 PointCloud2 迭代器

手写 pointer 算术遍历 PointCloud2 易错——先包 span 再 Map,offset 由 field 表驱动。迭代器与 span 混用时,每点 step 须读 point_step,别假设 sizeof(float)*4。版本升级 field 顺序变化时,单测 synthetic msg 先于 field 部署。

loan 归还后任何 Map 立即悬空——decode 须在 loan 有效期内完成,或 copy 出 owning 矩阵再 async。middleware 版本升级可能改 loan 语义,集成测试要覆盖 loan 路径与 copy 路径。别在 Map 上 hang 住 loan 超过 callback 所需时间,否则 middleware 背压异常。

10. 验收

  • synthetic PointCloud2 unit test 验证 field offset 与 Map 读值。
  • async 路径有 copy 或 lease 机制。
  • 未对齐 buffer 有 Unaligned 或 copy 策略。
  • plugin 边界 dtype/stride 文档与驱动一致。

12. 与 ROS message 版本

Humble/Jazzy message 定义变更可能改 field 顺序或 dtype——Map 代码硬编码 offset 会在升级时 silent break。启动 parse fields 表是长期解法;短期 pin message 版本并在 CI 用 recorded bag 回归。plugin 导出 C ABI 时不暴露 Eigen Map 类型,减少 layout 细节泄漏到 ABI 边界。双 Map 交叉写同一 buffer 时须测 alias,必要时 .eval() 到临时——向量化路径对 aliasing 更敏感。recorded bag 回归比 synthetic 更能抓 driver 真实 stride;CI nightly 跑一条短 bag Map 校验 intensity/xyz 范围,异常即 fail build。与 span 联用时,Map 维度须与 span 长度一致,入口 assert 集中——别 scatter 到十个 plugin。loan 归还后 Map 立即作废,async 路径 copy 或 lease 写进 driver 接口合同,联调时必问。Map 是零拷贝利器,layout 与 lifetime 写进文档比 silent 错数便宜。

driver 版本 bump 必跑 Map 回归:field offset、dtype、对齐、点数上限。silent 读错 intensity 比 crash 更难查——残差曲线仍收敛。changelog 要求 driver 团队标注 layout break;consumer 侧 synthetic + recorded bag 双测。写回 driver buffer 前确认 in-place 修改许可,loan 只读 buffer 上 Map 成 mutable 是契约破坏。

← 全部文章

johan's blog