【问题标题】:C++ portable array serializationC++ 可移植数组序列化
【发布时间】:2011-07-21 09:59:53
【问题描述】:

在我从事的一个项目中,我必须通过网络来回发送浮点/双精度数组,我使用 Boost.Asio 来处理网络内容,因为我需要异步通信,而这只是似乎是最好的(唯一真正的?)那里......

发送的数组是浮点数/双精度数,类型双方都知道。 AFAIK,浮点存储标准可能存在问题 + 与 long/int、endians 等相同的问题。

从逻辑上讲,发送的数组是密集矩阵,一端由 Eigen 处理,另一端使用 BLAS / [MKL|ATLAS] 等等,很可能需要其他用途,所以我要最通用的方式,似乎是在数组周围传递。

关键要求是高性能和可移植性,因为客户端和服务器都可以在 32 / 64 位的任意组合上运行,并且通信非常密集(实时监控,每隔一秒刷新一次)客户端),因此序列化开销本身必须是最小的。

就我目前的发现而言,这里要考虑的两个大玩家是 Boost.Serialization 和 Google 的 Protobuf。

BS 的最大优点是我已经在项目中大量使用了 Boost(尽管单元测试在 Google 测试中),而且使用 make_array() 序列化数组似乎非常简单。它最大的缺点是性能。

我发现 protobuf 的优势在于性能,所有的实验似乎都表明它在任何操作上都比 BS 高出 10-20 倍。我在 protobuf 文档中没有找到的是在消息中添加一个数组。它使用重复的字段,据我了解,我必须在数组的每个元素上使用MsgObject.repeatedProp.Add(const T&),这意味着,对于一个 10k 数组,需要对其进行 10k 次调用,这似乎也有点昂贵。

任何关于如何解决这个问题的建议都将受到高度赞赏,因为我对 C++ 的经验是有限的,而且我最近才在较长时间休息后重新开始编写它......

【问题讨论】:

  • 我无法评论用于添加项目的 C++ api(我在我的 C# 版本中避免了这种需要,但这对您没有帮助)- 只是说;如果您确实查看protobuf,这似乎是“打包数组”的一个很好的用例(它避免了每个元素的标头开销)。只是挤出一些额外性能的提示。
  • 如果你真的需要超高性能,而且你所有的客户端都是x86/x64,你可以直接把二进制数据写出来。浮点数和双精度数分别占用 4 和 8 个字节,没有填充(unline long double),因此它们在两个平台上看起来都一样。它并不完全是“便携的”,但实际上它应该可以工作。
  • @Kerrek SB 我曾想过,但我记得在某处读到 reinterpret_cast 依赖于编译器实现,这真的会影响可移植性......
  • @TC1:如果你能抽出半个小时,何不试试看是否行得通?我很确定投射到char* 明确定义(正是为了这个目的),所以我相信你不会失望。无论如何都要向传输添加校验和:[size|**|**|...|**|checksum].
  • Kerrek 是绝对正确的。您可以将 float* 或 double* 重新解释为 char*。这使您可以访问底层字节,并允许您执行诸如字节交换之类​​的事情。 (我已经在从 MSVC 到用于移动设备的 ARM 编译器的各种平台上完成了它。)

标签: c++ serialization protocol-buffers boost-serialization


【解决方案1】:

使用 protobufs,如果您使用“bytes”而不是“repeated int32”(或类似的)对数组进行编码,则可以取消对 10k 数组的 10k 次调用。 在您的代码中,如果您转换指针并使用 memcpy,通常会非常快。

【讨论】:

    【解决方案2】:

    除了 boost asio,你可能还想看看 boost.MPI(消息传递接口)。这使用 boost.serialization 进行幕后的序列化。

    Boost.mpi discusses boost.serialization 可能的性能优化。特别是使用

    • BOOST_CLASS_TRACKING(gps_position,track_never)
    • BOOST_CLASS_IMPLEMENTATION(gps_position,object_serializable)

    宏,以及 mpi 定义的宏

    • BOOST_IS_MPI_DATATYPE(gps_position)

    这些优化似乎效果很好,请参阅this 图表。

    我从未使用过 protobuff,所以对此我无话可说。

    【讨论】:

    • 我真的不需要 MPI,至少现在不需要,但是感谢 BS 的提示,我会检查一下。
    【解决方案3】:

    在 github 上有很好的例子。以下是一些可能的资源,用于往返于协议缓冲区特征和矩阵:

    • HAL 硬件抽象库(机器人)。
    • Tensorflow 对张量(nd 数组)做了很多相同的事情,但考虑到它们支持的扩展功能集,它们的实现可能会复杂得多。
    • NUbots 有另一个例子。
    • Chromium 好像也有例子。
    • ceres-solver 曾经支持 protobufs for matrices,但该功能已被删除。 (header)

    这里是 HAL 版本,很简单,看起来效果不错:

    Protobuf for storing a matrix:

    package hal;
    
    message MatrixMsg {
      required uint32 rows = 1;
      // columns deduced by division. Data stored as column major
      repeated double data = 2 [packed=true];
    }
    
    message VectorMsg {
      repeated double data = 1 [packed=true];
    }
    

    Loading into eigen and writing to protobuf:

    #pragma once
    
    #include <Eigen/Eigen>
    #include <HAL/Messages.pb.h>
    
    namespace hal {
    
    inline void ReadMatrix(const MatrixMsg &msg, Eigen::MatrixXd* mat) {
      mat->resize(msg.rows(),msg.data_size()/msg.rows());
      for(int ii = 0 ; ii < msg.data_size() ; ii++){
        mat->operator()(ii) = msg.data(ii);
      }
    }
    
    inline void ReadVector(const VectorMsg &msg, Eigen::VectorXd* vec) {
      vec->resize(msg.data_size());
      for(int ii = 0 ; ii < msg.data_size() ; ii++){
        vec->operator()(ii) = msg.data(ii);
      }
    }
    
    inline void WriteMatrix(const Eigen::MatrixXd &mat, MatrixMsg *msg) {
      msg->set_rows(mat.rows());
      msg->mutable_data()->Reserve(mat.rows()*mat.cols());
      for(int ii = 0 ; ii < mat.cols()*mat.rows() ; ii++){
        msg->add_data(mat(ii));
      }
    }
    
    inline void WriteVector(const Eigen::VectorXd &mat, VectorMsg *msg) {
      msg->mutable_data()->Reserve(mat.rows());
      for(int ii = 0 ; ii < mat.rows() ; ii++){
        msg->add_data(mat(ii));
      }
    }
    
    }  // namespace hal
    

    但是,如果您要跨库执行此操作,则需要考虑更多问题。还有另一个库有一个很好的实现,其中有类型、宽度、高度的枚举,然后是字节数组、rowmajor/colmajor 等。但是我无法再次找到它。这些必须考虑并包含在 protobuf 中,以便跨矩阵库兼容。对于如何做到这一点的一些想法,HAL image protobufimage cpp 可能会有所帮助,它们从 opencv 源读/写,也可以被认为是一个矩阵。

    【讨论】:

      猜你喜欢
      • 2012-07-15
      • 1970-01-01
      • 1970-01-01
      • 2016-05-19
      • 2012-05-24
      • 2015-06-03
      • 1970-01-01
      • 1970-01-01
      • 2011-06-11
      相关资源
      最近更新 更多