是否可以重用binary_oarchive实例?

时间:2011-06-13 10:54:24

标签: c++ performance boost boost-serialization

我的问题与五年前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个对象/秒,但是,然后,这些对象依次序列化,这不是我想要的:我想要清除并且在每个对象被序列化之后重用相同的缓冲区(寻找它的开头)。

我该怎么做?

3 个答案:

答案 0 :(得分:2)

是的,从某种意义上说,你绝对可以重复使用它。 oarchive简单地包装了一个流,并且不知道流的数据发生了什么,所以诀窍是实现你自己的流(这不是很有趣),以允许你“重置”实际的底层数据流。我之前写过类似的东西,它的功能很棒。

但有些问题需要注意:

oarchive不会继续写出标题信息(因为如果它持续存在,它将所有内容视为一个大流),那么你将要禁用标题:

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

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

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

依旧..对于你所有的类型,它们的所有std ::向量,它们的所有std :: shared_ptrs等等。这是至关重要的。否则,如果您使用共享的iarchive,则只能读回您的流,并按照序列化的顺序读取它们。

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

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

  • 您需要编写自己的流(因此可以重定向/重置)
  • 禁用存档标题
  • 拥有注册类型

所以是的,重用oarchive / iarchive是可能的,但这有点痛苦。一旦你把它整理好了,它就非常棒了。

答案 1 :(得分: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序列化传递指针到缓冲区。 Reader从同一个缓冲区反序列化指针(读写器之间共享相同的缓冲区)

#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"; };
};

答案 2 :(得分:0)

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