4

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

我将我的对象序列化为字节缓冲区,如下所示:

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 个对象/秒,但是,对象会一个接一个地序列化,这不是我想要的:我想清除并在每个对象被序列化后重用相同的缓冲区(寻找它的开头)。

我怎样才能做到这一点?

4

3 回答 3

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"; };
};
于 2013-09-23T15:34:12.803 回答
2

是的,从某种意义上说,你绝对可以重复使用它。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::vectors,它们的所有 std::shared_ptrs 等等。这很重要。否则,如果您使用共享 iarchive 并以与序列化完全相同的顺序读取它们,您将只能读回您的流。

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

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

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

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

于 2011-06-13T12:57:35.433 回答
0

一种无需进一步研究的解决方案是存储字符串的最后一个长度,并使用最后一个长度和实际长度获取子字符串(将是添加到输出中的最后一个字符串)。每 10 或 100 次迭代,您都可以重新开始,binary_oarchive不要在serial_str.

于 2011-06-13T12:04:27.667 回答