【问题标题】:Is it possible to reuse a binary_oarchive instance?是否可以重用 binary_oarchive 实例?
【发布时间】:2011-06-13 10:54:24
【问题描述】:

我的问题与五年前在this thread 中讨论的问题相同(没有好的答案)。

我正在将我的对象序列化到一个字节缓冲区中,如下所示:

std::string serial_str;
for (i = 1; i < 10000; i++)
{
    boost::iostreams::back_insert_device<std::string> inserter(serial_str);
    boost::iostreams::stream<boost::iostreams::back_insert_device<std::string> > s(inserter);
    boost::archive::binary_oarchive oa(s);

    oa << obj;

    s.flush();

    // code to send serial_str's content to another process, omitted.

    serial_str.clear(); // clear the buffer so it can be reused to serialize the next object
}    

当我在循环中执行此操作时,性能非常糟糕:我每秒获得约 14,000 个对象。

我已将问题归结为 binary_oarchive 的重新创建。如果我只是在循环中使用相同的存档实例写入相同的字符串,我会得到 ~220,000 个对象/秒,但是,对象会一个接一个地序列化,这不是我想要的:我想清除并在每个对象被序列化后重用相同的缓冲区(寻找它的开头)。

我该怎么做?

【问题讨论】:

  • 那么,如果您要在写入内容后立即丢弃其内容,那么拥有缓冲区有什么意义呢?
  • 循环中究竟包含什么?如果您声明 serial_str 也不必清除它,因为它是在每个循环中创建的。
  • @Tomalak 抱歉,我应该提到在序列化每个对象之后,我使用 boost::interprocess::message_queue 将其内容发送到另一个进程。
  • @Omer:啊哈!是的,你应该有。 :)
  • @Diego @Tomalak 抱歉,我已经更新了我的代码来解决这些问题。

标签: c++ performance boost boost-serialization


【解决方案1】:

是的,从某种意义上说,您绝对可以重复使用它。 oarchive 只是封装了一个流,并且不知道流的数据发生了什么,所以诀窍是实现您自己的流(这并不好玩),以允许您“重置”实际的底层数据流。我以前写过类似的东西,效果很好。

需要注意的一些问题:

存档不会继续写出标题信息(因为如果它持续存在,它将把所有内容都视为一个大流),因此您需要禁用标题:

boost::archive::binary_oarchive oa(s, boost::archive::no_codecvt | boost::archive::no_header);

此外,由于您正在重用一个存档,因此您必须非常小心地管理其内部类型表。如果您要序列化的只是整数、浮点数等,那么您会没事的,但是一旦您开始序列化类、字符串等,您就不能依赖存档在重用时使用的默认类型枚举像这样的存档。 Boost 文档并没有真正涉及到这一点,但对于任何复杂的事情,您需要为存档将遇到的 每个 类型执行以下操作:

oa.template register_type<std::string>();
oa.template register_type<MyClass>();
oa.template register_type<std::shared_ptr<MyClass> >();

等等.. 对于所有类型,它们的所有 std::vector,它们的所有 std::shared_ptrs 等等。这是至关重要的。否则,如果您使用共享 iarchive 并以与序列化完全相同的顺序读取它们,您将只能读回您的流。

结果是您的 iarchive 需要以与它们的 oarchive 完全相同的方式和顺序注册所有类型(我使用 mpl 编写了一些方便的助手来帮助我解决这个问题)。

通过 iarchive 重新序列化也可以共享同一个 iarchive,但是所有相同的条件都适用:

  • 您需要编写自己的流(以便可以重定向/重置)
  • 禁用存档标题
  • 有寄存器类型

所以是的,重复使用 oarchive/iarchive 是可能的,但它有点痛苦。不过,一旦你把它整理好,那就太棒了。

【讨论】:

  • 谢谢,这很有帮助!您能否详细说明为什么我必须编写自己的流,并且不能只重用 boost 库中的一个,以及如何构建它?我的自定义流与 std::stringstream 有何不同?
  • 如果要将其指向不同的目标,则需要使用自己的流。对我来说,我在考虑文件,但它仍然适用于您的代码。您需要为每个新字符串创建一个新流,因此如果您不编写自己的流,这意味着您需要为每个新字符串创建一个新存档。查看您的代码,它建议您通过清除它来重用字符串,所以这就足够了。如果您必须切换到不同的字符串,那么您需要一个自定义流来允许这样做。但是,我所说的标题和类型注册仍然适用。
  • 如果您的反序列化和序列化保持完美同步(因此没有持久存储)直接输出-> 输入,您甚至可以避免更改标头和处理类型注册。然而,这是有风险的并且非常脆弱。 (假设您在另一边有一个 iarchive 正在读取您的字符串)
  • Boost.Iostreams 很有趣
【解决方案2】:

这是我想出的解决方案。 它不需要实现您自己的流,并允许为每次下一个序列化重用相同的内存块。 假设您为序列化安排了以下结构:

boost::iostreams::basic_array<char> sink; // target buffer 
boost::iostreams::stream<boost::iostreams::basic_array<char> > os;  // stream wrapper around it
boost::archive::binary_oarchive oa;  // archive which uses this stream

然后要重用相同的缓冲区,只需重新打开流:

os.close();
os.open(sink);

应该与更改流中的一些内部指针一样快。不过,我还没有测试过实际速度。

试用代码: Writer 序列化传递的指向缓冲区的指针。 读取器从同一个缓冲区反序列化指针(读取器和写入器共享同一个缓冲区)

#include <iostream>
#include <fstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/access.hpp>

class A;
class Writer {
    char *buf;
    int len;
    boost::iostreams::basic_array<char> sink;
    boost::iostreams::stream<boost::iostreams::basic_array<char> > os;
    boost::archive::binary_oarchive oa;
public:
    Writer(char *_buf, int _len): buf(_buf), len(_len), sink(buf, len), os(sink), oa(os) {}
    void write(A* a) {
        oa << a;
    }
    void reset() {
        os.close();
        os.open(sink);
    }
};
class Reader {
    char *buf;
    int len;
    boost::iostreams::basic_array_source<char> src;
    boost::iostreams::stream<boost::iostreams::basic_array_source<char> > is;
    boost::archive::binary_iarchive ia;
public:
    Reader(char *_buf, int _len): buf(_buf), len(_len), src(buf, len), is(src), ia(is) {}
    A* read() {
        A* a;
        ia >> a;
        return a;
    }
    void reset() {
        is.close();
        is.open(src);
    }
};

int main(int argc, char **argv) {
    // to memory
    char buffer[4096] = {0};

    Writer w(buffer, sizeof(buffer));
    A *a1 = new A(5);
    w.write(a1);

    Reader r(buffer, sizeof(buffer));
    A *a2 (NULL);
    a2 = r.read();

    assert(*a1 == *a2);
    std::cout << "Simple ok\n";

    // test reuse
    w.reset();
    r.reset();

    A *a3 (NULL);
    w.write(new A(10));
    a3 = r.read();

    assert(*a3 == A(10));
    std::cout << "Reuse ok\n";
};

class A
{
private:
  friend class boost::serialization::access;
  int i;

  template <typename Archive>
  void serialize(Archive& ar, const unsigned int version) {
    std::cout << "serialize A\n";
    ar & i;
  }
public:
  A(): i(0) {};
  A(int _i): i(_i) {};
  virtual bool operator==(const A&r) { return i == r.i; };

  virtual ~A() {};
  virtual void whoa() {std::cout << "I am A!\n";};
  virtual const char* me() { return "A"; };
};

【讨论】:

    【解决方案3】:

    一种无需进一步研究的解决方案是存储字符串的最后一个长度,并使用最后一个长度和实际长度获取子字符串(将是添加到输出中的最后一个字符串)。每 10 或 100 次迭代,您可以重新启动 binary_oarchive,以免在 serial_str 中积累太多过去的编码对象。

    【讨论】:

    • 谢谢,我考虑过,但似乎应该有一个开箱即用的解决方案?
    • 好的,这里的问题是back_insert_device是不可搜索的,所以你不能搜索到开头。
    • 哦,我!我认为 boost::iostreams::basic_array 是可搜索的,我会尝试使用它,看看会发生什么。
    • 是的,我正要建议这样做,但我没有提升流的经验。另外,请注意,创建binary_oarchive 的成本并不高,因为它只使用给定流中的streambuf(存储对它的引用),因此只需缠绕流并创建新的binary_oarchive 即可保持性能。跨度>
    猜你喜欢
    • 2010-09-14
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-13
    • 1970-01-01
    相关资源
    最近更新 更多