{技术}介绍一下自己开发的 C++ 序列化框架

435 views
Skip to first unread message

rockeet

unread,
Mar 4, 2010, 11:10:29 PM3/4/10
to TopLanguage
目前仅实现了二进制格式序列化,未实现文本格式序列化

首先注意:vc6太古老,不符合C++规范,无法使用该框架
该框架代码在vc2008, gcc4.x 中编译、测试通过

特点:
1. 高性能,速度非常快,比你能找到的同类产品至少快一个数量级(包括boost.serailization 和
google.protobuf)
2. 在网络通讯,数据库存储中非常好用。
3. 预先支持所有基本类型,所有stl容器类型(除stack/queue之外)
4. 支持变长int32/uint32/int64/uint64
5. 支持stl::pair,boost::tuple
6. 可选的版本控制,而非强制
a) 对于小对象,通常不需要版本控制
b) boost::serialization的版本号是强制的,当初我设计这个序列化框架就是因为boost不能省略版本号
7. 非侵入式设计,不污染名字空间
8. 声明式语法,简单,可靠
9. …….

易用性

用代码说明问题最简洁,看这个例子:
struct MyData1 {
int a, b, c;
var_int32_t d; // d.t is int32 value
var_uint64_t e; // d.t is uint64 value
std::string f;
std::map<std::string, int> g;
std::set<int> h;

// 声明序列化,无版本控制,最简洁的声明,后面几个稍微复杂点
DATA_IO_LOAD_SAVE(MyData1, &a&b&c&d&e&f&g&h)
};
struct MyData2 {
int a, b, c;
var_int32_t d;
var_uint64_t e;
std::string f;
std::map<std::string, int> g;
std::set<int> h;

// 声明序列化,有版本控制
DATA_IO_LOAD_SAVE_V(MyData2,
1, // 当前版本
&a&b&c&d&e&f&g&h
)
};
struct MyData3 {
int a, b, c;
boost::int32_t d;
boost::uint64_t e;
std::string f;
std::map<std::string, int> g;
std::set<int> h;
std::multiset<int> i;
unsigned version;

// 声明序列化,有版本控制
DATA_IO_LOAD_SAVE_V(MyData3,
2, // 当前版本
&a
&b
&c
&as_var_int(d) // d 声明为int32_t, 但是作为var_int32_t 来存储
&as_var_int(e) // e 声明为uint64_t, 但是作为var_uint64_t 来存储
&f
&g
&h
&vmg.since(2, i) // 版本2 新增了成员i
&vmg.get_version(version) // 如果需要,将版本值存入version 成员
)
};

int main(int argc, char* argv[])
{
PortableDataOutput<AutoGrownMemIO> output;
PortableDataInput<MemIO> input;

output.resize(1024); // 可选,如果没有这一行最多就是多拷贝几次内存,相当于使用vector.reserve
MyData1 d1;

// set d1 values
// ...
MyData2 d2;

// set d2 values
// ...

MyData3 d3;
// set d3 values
// ...

output << d1 << d2 << d3; // 存储

input.set(output.begin(), output.current());
input >> d1 >> d2 >> d3; // 载入

//----------------------------------
// operator& 与operator<< 等效
output & d1 & d2 & d3; // 存储

input.set(output.begin(), output.current());

// operator& 与operator>> 等效
input & d1 & d2 & d3; // 载入
}

模仿这段代码,可以完成大部分的现实需求,如果有更多的需求,可以使用该框架的高级功能。例如,系统中已经定义了一些数据结构,但又不能修改现有代码,
怎样给它们增加序列化能力呢?请看如下代码:

// in system header, can not change
struct SysData1 {
int a;
unsigned b;
string c;
};
// must in the same name space of SysData1
DATA_IO_LOAD_SAVE_E(SysData1, &a&b&c)

如果现存的对象需要版本控制,参考如下代码:
struct SysData2 {
int a;
unsigned b;
string c;
};
// must in the same name space of SysData2
DATA_IO_LOAD_SAVE_EV(SysData2,
2, current version
&a
&b
&vmg.since(2, c) // version 2 add new field c
)

* 优化
对于简单对象,如:
struct Simple {
int a, b;
double c, d;
DATA_IO_LOAD_SAVE(Simple, &a&b&c&d)
};
使用Native*时,会自动推导为:可memcpy,vector<Simple> 也可 memcpy。

DataIO 序列化框架使用非成员函数 DataIO_loadObject/DataIO_saveObject 来载入和存储对象,这样做的好处
有以下几点:
1. 非侵入,对象类型和加载/存储函数可以分离定义
否则无法为不可更改代码的对象增加序列化能力
2. 这两个函数可以定义在任何名字空间
根据C++的名字查找规则,只要在调用环境和每个参数所在的名字空间中有相应的匹配函数,就会使用该函数。我们需要有效地利用这一
点。

3. DataIO_loadObject/DataIO_saveObject这两个函数名较长,并且罕见
因此不会与系统中的其他标识符发生冲突。(对比boost::serialization中的serialize函数,它就比较容
易和其他名字发生冲突,serialize太常见了)。

*性能

与 boost.serialization.binary 做对比,以下结果均有相应测试代码
1. 运行速度
复杂对象(如 list<pair<int,MyData1> >甚至更复杂)快5~10倍
简单对象(如 vector<Simple>)快20~300倍
2. 编译速度快 3~4 倍
3. 代码尺寸小 3~5 倍

在上面的代码中可以看到几个陌生的名字:MemIO, AutoGrownMemIO,PortableDataOutput,
PortableDataInput…

代码中没有提到MinMemIO,因为MinMemIO没有越界检查,只有在非常简单,完全可控的情况下,才能使用它。因为没有越界检查,它的性能非常
好,在大多数情况下相当于手工的memcpy序列化。

使用MemIO稍微慢一点,但是有越界检查,读取时越界会抛出EndOfFileException异常,写入越界时会抛出
OutOfSpaceException异常。

使用AutoGrownMemIO,在save时,碰到边界会自动增加内存(相当于vector.push_back自动增加内存),也可以使用
resize预先分配内存(相当于vector.reserve/resize)。

这个几个MemIO类都非常简单,速度快是很自然的。

在PortableDataOutput,PortableDataInput中,如果机器字节序是LittleEndian,需要交换字节序,这一
点,在新版的vc中和gcc中,会直接映射到一条指令:bswap。所以也不会有性能问题。

对于var_int的存储,无符号数,每个字节包含7个有效位,余下一位表示是否需要读取下一个字节。因此0~127仅需要一个字节,0~2^14-1
需要两个字节,等等。对于有符号数,最低有效位存储符号,其余位存储绝对值。所有stl容器和string的尺寸就是用var_uint32_t存储
的。

该框架中,同时实现了StreamBuffer,可以为任意Stream增加一层缓冲,往缓冲里面序列化数据的效率和MemIO系列是一样的,不同之处
在于当缓冲耗尽或填满时会调用真实Stream的读写方法。这比起通常很多实现中将BufferedStream作为一个抽象,在读取哪怕一个字节时也
需要一个虚函数调用,速度要快得多。
扩展应用

使用该序列化框架,我实现了一个不需要IDL的RPC。
使用该序列化框架,对Berkeley DB进行包装,可以让它象标准容器一样使用,免除了复杂的编码。

项目地址:http://code.google.com/p/febird

Shuo Chen

unread,
Mar 4, 2010, 11:31:48 PM3/4/10
to TopLanguage
用版本号会带来无穷无尽的问题。

rockeet

unread,
Mar 5, 2010, 12:48:07 AM3/5/10
to TopLanguage
不可避免的情况下,版本号是最简单的一种,但一般不能实现向前兼容(旧代码读新数据)
bitmap要复杂一点,可以实现向前兼容(旧代码读新数据)
更复杂的,太复杂了……

On 3月5日, 下午12时31分, Shuo Chen <giantc...@gmail.com> wrote:
> 用版本号会带来无穷无尽的问题。

jinhu wang

unread,
Mar 5, 2010, 12:56:08 AM3/5/10
to pon...@googlegroups.com
我一般就用我那个stream做序列化。

Yuan Liu

unread,
Mar 5, 2010, 12:58:46 AM3/5/10
to pon...@googlegroups.com
弱问,没看太明白

能详细说说为啥会快这么多么?比如举个用例

2010/3/5 rockeet <roc...@gmail.com>



--
Yuan Liu

rockeet

unread,
Mar 5, 2010, 1:06:45 AM3/5/10
to TopLanguage
1. 抛弃了std::*stream
2. 细节上的优化
可以参考我的这几篇文章:
http://blog.csdn.net/whinah/archive/2009/04/04/4048283.aspx
http://blog.csdn.net/whinah/archive/2009/04/06/4052715.aspx
http://blog.csdn.net/whinah/archive/2009/04/06/4052720.aspx
http://blog.csdn.net/whinah/archive/2009/04/11/4065014.aspx

On 3月5日, 下午1时58分, Yuan Liu <xiaolang...@gmail.com> wrote:
> 弱问,没看太明白
>
> 能详细说说为啥会快这么多么?比如举个用例
>

> 2010/3/5 rockeet <rock...@gmail.com>


>
>
>
> > 目前仅实现了二进制格式序列化,未实现文本格式序列化
>
> > 首先注意:vc6太古老,不符合C++规范,无法使用该框架
> > 该框架代码在vc2008, gcc4.x 中编译、测试通过
>
> > 特点:
> > 1. 高性能,速度非常快,比你能找到的同类产品至少快一个数量级(包括boost.serailization 和
> > google.protobuf)
> > 2. 在网络通讯,数据库存储中非常好用。
> > 3. 预先支持所有基本类型,所有stl容器类型(除stack/queue之外)
> > 4. 支持变长int32/uint32/int64/uint64
> > 5. 支持stl::pair,boost::tuple
> > 6. 可选的版本控制,而非强制
> > a) 对于小对象,通常不需要版本控制
> > b) boost::serialization的版本号是强制的,当初我设计这个序列化框架就是因为boost不能省略版本号
> > 7. 非侵入式设计,不污染名字空间
> > 8. 声明式语法,简单,可靠

> > 9. .......

> > PortableDataInput...

Betterman

unread,
Mar 5, 2010, 1:11:00 AM3/5/10
to pon...@googlegroups.com
挺好啊.

1. 有计划支持多语言么?
2. 在实际应用中, 想复用其他 team 的代码, 能这样用么:
struct A {
...
};
struct B {
A a;
}
3. 目前看来, 这框架融合了挺多其他东西/功能的, 感觉上能再简化/简洁一些就好了.


2010/3/5 rockeet <roc...@gmail.com>:


> 目前仅实现了二进制格式序列化,未实现文本格式序列化
>
> 首先注意:vc6太古老,不符合C++规范,无法使用该框架
> 该框架代码在vc2008, gcc4.x 中编译、测试通过
>
> 特点:
> 1. 高性能,速度非常快,比你能找到的同类产品至少快一个数量级(包括boost.serailization 和
> google.protobuf)
> 2. 在网络通讯,数据库存储中非常好用。
> 3. 预先支持所有基本类型,所有stl容器类型(除stack/queue之外)
> 4. 支持变长int32/uint32/int64/uint64
> 5. 支持stl::pair,boost::tuple
> 6. 可选的版本控制,而非强制
> a) 对于小对象,通常不需要版本控制
> b) boost::serialization的版本号是强制的,当初我设计这个序列化框架就是因为boost不能省略版本号
> 7. 非侵入式设计,不污染名字空间
> 8. 声明式语法,简单,可靠

> 9. .......

> PortableDataInput...


>
> 代码中没有提到MinMemIO,因为MinMemIO没有越界检查,只有在非常简单,完全可控的情况下,才能使用它。因为没有越界检查,它的性能非常
> 好,在大多数情况下相当于手工的memcpy序列化。
>
> 使用MemIO稍微慢一点,但是有越界检查,读取时越界会抛出EndOfFileException异常,写入越界时会抛出
> OutOfSpaceException异常。
>
> 使用AutoGrownMemIO,在save时,碰到边界会自动增加内存(相当于vector.push_back自动增加内存),也可以使用
> resize预先分配内存(相当于vector.reserve/resize)。
>
> 这个几个MemIO类都非常简单,速度快是很自然的。
>
> 在PortableDataOutput,PortableDataInput中,如果机器字节序是LittleEndian,需要交换字节序,这一
> 点,在新版的vc中和gcc中,会直接映射到一条指令:bswap。所以也不会有性能问题。
>
> 对于var_int的存储,无符号数,每个字节包含7个有效位,余下一位表示是否需要读取下一个字节。因此0~127仅需要一个字节,0~2^14-1
> 需要两个字节,等等。对于有符号数,最低有效位存储符号,其余位存储绝对值。所有stl容器和string的尺寸就是用var_uint32_t存储
> 的。
>
> 该框架中,同时实现了StreamBuffer,可以为任意Stream增加一层缓冲,往缓冲里面序列化数据的效率和MemIO系列是一样的,不同之处
> 在于当缓冲耗尽或填满时会调用真实Stream的读写方法。这比起通常很多实现中将BufferedStream作为一个抽象,在读取哪怕一个字节时也
> 需要一个虚函数调用,速度要快得多。
> 扩展应用
>
> 使用该序列化框架,我实现了一个不需要IDL的RPC。
> 使用该序列化框架,对Berkeley DB进行包装,可以让它象标准容器一样使用,免除了复杂的编码。
>
> 项目地址:http://code.google.com/p/febird
>

--
--

To be a betterman.

jiang yu

unread,
Mar 5, 2010, 1:16:52 AM3/5/10
to pon...@googlegroups.com
我曾经用过boost::serailization——很多特性,但是非常慢,代码膨胀的快;改用std::iostream,无非是不用特性罢了,还是很慢;到现在自己写了个类iostream的来做序列化,要比std::iostream快100倍(windows多核多线程),因为std::iostream要支持多线程访问同一个对象吧,比如cout,自己写的就是单线程访问的。
可以用来做存储,但是消息仍然不用,只用POD的struct,你说是刻意追求效率吗,效率差别有多少呢,我看也差不多,就是为了规整代码,并且怕被人误用。
所以感觉boost::serailization非常鸡肋,如果是轻量级的不如用std::iostream,多写点代码何必引入它,重量级的使用它又顾及不了。
写的时候没有看楼主的代码,希望这个经验对楼主有帮助。

在 2010年3月5日 下午1:48,rockeet <roc...@gmail.com>写道:

Shuo Chen

unread,
Mar 5, 2010, 1:17:30 AM3/5/10
to TopLanguage
学 protobuf,用field number.

rockeet schrieb:

rockeet

unread,
Mar 5, 2010, 1:28:44 AM3/5/10
to TopLanguage
fieldnum 在实现上肯定也使用了类似 bitmap 的技术
要在我这个框架内用fieldnum的话,就要修改声明语法,并且会使系统太过复杂

rockeet

unread,
Mar 5, 2010, 1:35:38 AM3/5/10
to TopLanguage
可以用来做存储,但是消息仍然不用,只用POD的struct,你说是刻意追求效率吗,效率差别有多少呢,我看也差不多,就是为了规整代码,并且怕被人
误用。
^^^^^^^^^^^^^^^^^^^^^^^^
该系统做消息序列化再合适不过了!
比起 boost::serialization,编译速度、代码膨胀、运行速度都要好得多:

On 3月5日, 下午2时16分, jiang yu <yu.jiang....@gmail.com> wrote:
> 我曾经用过boost::s


> erailization——很多特性,但是非常慢,代码膨胀的快;改用std::iostream,无非是不用特性罢了,还是很慢;到现在自己写了个类iostream的来做序列化,要比std::iostream快100倍(windows多核多线程),因为std::iostream要支持多线程访问同一个对象吧,比如cout,自己写的就是单线程访问的。
> 可以用来做存储,但是消息仍然不用,只用POD的struct,你说是刻意追求效率吗,效率差别有多少呢,我看也差不多,就是为了规整代码,并且怕被人误用。
> 所以感觉boost::s
> erailization非常鸡肋,如果是轻量级的不如用std::iostream,多写点代码何必引入它,重量级的使用它又顾及不了。
> 写的时候没有看楼主的代码,希望这个经验对楼主有帮助。
>

rockeet

unread,
Mar 5, 2010, 1:41:56 AM3/5/10
to TopLanguage
1. 多语言支持是可以实现的,但目前尚未考虑
2. struct A {
...
DATA_IO_LOAD_SAVE(A,...)
};
struct B {
A a; // 当然可以
vector<A> b; // 也可以
DATA_IO_LOAD_SAVE(B, &a&b)
};
当然能这样用,前提是被引用的类/结构必须首先支持序列化(已用DATA_IO_LOAD_SAVE*声明过)
3. 欢迎提出意见

On 3月5日, 下午2时11分, Betterman <betterman9...@gmail.com> wrote:
> 挺好啊.
>
> 1. 有计划支持多语言么?
> 2. 在实际应用中, 想复用其他 team 的代码, 能这样用么:
> struct A {
> ...
> };
> struct B {
> A a;
> }
> 3. 目前看来, 这框架融合了挺多其他东西/功能的, 感觉上能再简化/简洁一些就好了.
>

> 2010/3/5 rockeet <rock...@gmail.com>:

Shuo Chen

unread,
Mar 5, 2010, 6:48:09 AM3/5/10
to TopLanguage
用 field number 为的是系统无缝升级,除非你全部的应用协议都像 TCP/IP 一样稳定,否则很快就会遇到头疼的事情。
你这个比 google.protobuf 快多少?

rockeet

unread,
Mar 5, 2010, 7:55:33 AM3/5/10
to TopLanguage
快5~10倍

> > > > > 用版本号会带来无穷无尽的问题。- 隐藏被引用文字 -
>
> - 显示引用的文字 -

Feng Yu

unread,
Mar 5, 2010, 7:58:15 AM3/5/10
to pon...@googlegroups.com
another wheel!

专注 高性能容错分布式服务器的研究和实现
http://blog.yufeng.info


2010/3/5 rockeet <roc...@gmail.com>

Shuo Chen

unread,
Mar 5, 2010, 8:01:50 AM3/5/10
to TopLanguage
有没有对比的代码?

rockeet

unread,
Mar 5, 2010, 1:09:47 PM3/5/10
to TopLanguage
暂时没有与 protobuf 的对比代码,5~10倍只是一个非常保守的估计
仅作性能比较的话,我这个系统应该跟 memcpy 比,其它类似系统都得靠边站(操作系统、驱动程序级的 ZeroCopy 除外)
有人在他的系统中用手工 memcpy 代替 boost.serialization,仅获得了 10 倍的性能提升(可以google之),而我这
个系统就轻松达到甚至超过了这个数

如要 pk 功能(旧代码读新数据,方便添加、删除成员等),我这个确实很不如 protobuf

> > > - 显示引用的文字 -- 隐藏被引用文字 -
>
> - 显示引用的文字 -

Shuo Chen

unread,
Mar 5, 2010, 7:39:41 PM3/5/10
to TopLanguage
就是说速度这个数字不是测出来,而是估出来?
那么生成的消息的大小呢?与protobuf相比,是大还是小?
如果speed/size都全面超越protobuf,那protobuf卖点在哪儿?
你这个能做消息转发(routing)吗?比如一个程序不用知道消息的完整定义,只知道消息头就能转发整个消息。
rockeet schrieb:

sagasw

unread,
Mar 5, 2010, 9:04:44 PM3/5/10
to pon...@googlegroups.com
几点建议,不算是大问题,仅供参考。

1)项目介绍的英文应该更加精准一些,比如“C++ template library, serialization, non-idl rpc, etc”,其实说的不太准确。比如你所追赶的项目protobuff介绍如下:
Protocol Buffers are a way of encoding structured data in an efficient yet extensible format. Google uses Protocol Buffers for almost all of its internal RPC protocols and file formats.
简明扼要,一看就懂了。
另外正文介绍的英文也是最好推敲一下。

2)建议你把这个项目转发到http://groups.google.com/group/protobuf,相应的关注会多一些,有比较才有进步。

3)一定要有一个how-to的wiki页面,how to install it,how to use it

4)应该有一个稍微具体的性能比较程序。用数据说话是王道。

项目很有趣,我暂时用不上,所以就在这里鼓鼓掌加加油了。

------------------------------------
C++, Lua, living in Dalian
http://sunxiunan.com/
http://twitter.com/sagasw
------------------------------------


2010/3/6 rockeet <roc...@gmail.com>

Ian Yang

unread,
Mar 5, 2010, 10:04:09 PM3/5/10
to pon...@googlegroups.com
这宏怎么跟 febird 一样?

2010/3/6 sagasw <sag...@gmail.com>



--
Sincerely, Ian Yang


Ian Yang

unread,
Mar 5, 2010, 10:09:16 PM3/5/10
to pon...@googlegroups.com
是不是就是这个啊:

http://code.google.com/p/febird/

2010/3/6 Ian Yang <doit...@gmail.com>



--
Sincerely, Ian Yang


rockeet

unread,
Mar 6, 2010, 1:13:20 AM3/6/10
to TopLanguage
对,我就是febird

On 3月6日, 上午11时09分, Ian Yang <doit....@gmail.com> wrote:
> 是不是就是这个啊:
>
> http://code.google.com/p/febird/
>

> 2010/3/6 Ian Yang <doit....@gmail.com>


>
>
>
>
>
> > 这宏怎么跟 febird 一样?
>
> > 2010/3/6 sagasw <sag...@gmail.com>
>
> > 几点建议,不算是大问题,仅供参考。
>

> >> 1)项目介绍的英文应该更加精准一些,比如“*C++ template library, serialization, non-idl rpc,
> >> etc <http://code.google.com/p/febird/>”,其实*说的不太准确。比如你所追赶的项目protobuff介绍如下:


> >> Protocol Buffers are a way of encoding structured data in an efficient yet
> >> extensible format. Google uses Protocol Buffers for almost all of its
> >> internal RPC protocols and file formats.
> >> 简明扼要,一看就懂了。
> >> 另外正文介绍的英文也是最好推敲一下。
>
> >> 2)建议你把这个项目转发到http://groups.google.com/group/protobuf,相应的关注会多一些,有比较才有进步。
>
> >> 3)一定要有一个how-to的wiki页面,how to install it,how to use it
>
> >> 4)应该有一个稍微具体的性能比较程序。用数据说话是王道。
>
> >> 项目很有趣,我暂时用不上,所以就在这里鼓鼓掌加加油了。
>
> >> ------------------------------------
> >> C++, Lua, living in Dalian
> >>http://sunxiunan.com/
> >>http://twitter.com/sagasw
> >> ------------------------------------
>

> >> 2010/3/6 rockeet <rock...@gmail.com>

> Sincerely, Ian Yang- 隐藏被引用文字 -
>
> - 显示引用的文字 -

rockeet

unread,
Mar 6, 2010, 1:21:33 AM3/6/10
to TopLanguage
1. speed/size 肯定全面超越protobuf

2. protobuf的优势于它的功能全面:多语言、数据兼容性好

3. 不能做消息转发,除非你此之上添加消息边界标识
消息转发实际上跟这个不是一回事,两者完全是正交的,转发消息并不需要知道消息内容的格式

rockeet

unread,
Mar 6, 2010, 1:23:11 AM3/6/10
to TopLanguage
是,在推广方面,是做得很不够

On 3月6日, 上午10时04分, sagasw <sag...@gmail.com> wrote:
> 几点建议,不算是大问题,仅供参考。
>

> 1)项目介绍的英文应该更加精准一些,比如“*C++ template library, serialization, non-idl
> rpc, etc<http://code.google.com/p/febird/>
> ”,其实*说的不太准确。比如你所追赶的项目protobuff介绍如下:


> Protocol Buffers are a way of encoding structured data in an efficient yet
> extensible format. Google uses Protocol Buffers for almost all of its
> internal RPC protocols and file formats.
> 简明扼要,一看就懂了。
> 另外正文介绍的英文也是最好推敲一下。
>
> 2)建议你把这个项目转发到http://groups.google.com/group/protobuf,相应的关注会多一些,有比较才有进步。
>
> 3)一定要有一个how-to的wiki页面,how to install it,how to use it
>
> 4)应该有一个稍微具体的性能比较程序。用数据说话是王道。
>
> 项目很有趣,我暂时用不上,所以就在这里鼓鼓掌加加油了。
>
> ------------------------------------

> C++, Lua, living in Dalianhttp://sunxiunan.com/http://twitter.com/sagasw
> ------------------------------------
>
> 2010/3/6 rockeet <rock...@gmail.com>
>
>
>

Reply all
Reply to author
Forward
0 new messages