ToplingDB 的序列化框架:簡介
ToplingDB 的 分散式 Compact 中 Client-Server 互動,使用了 topling-zip 中的序列化框架,該序列化框架初版完成於 2006 年,後來命名為 febird 庫在 google code 上開源,再後來 google code 停止服務,febird 遷移到 github,有段時間重新命名為 nark,之後重新命名為 terark,目前 topling-zip 中程式碼的 namespace 仍是 terark。從 2006 年至今,除 namespace 名稱之外,該序列化框架的介面一直保持穩定,2016 年的時候,針對 C++11 進行了模板推導相關的大幅最佳化,但仍保持了介面的穩定。以下為原文正文,排版有輕微改動。
原文: 最便捷、最強大、速度最快的C++序列化框架
作者: rockeet
發表日期: 2008年11月07日
分類: C++序列化
評論: 11 條
閱讀次數: 6,336 次
迄今為止,我還沒找到更優雅、更高效的 C++ 序列化方案,包括但不限於 boost.serialization。如果你發現了更快的,或者更易用的C++原生序列化,請告訴我……
(一)我這個序列化庫的特點
- 高效能,速度非常快,比你能找到的同類產品至少快一個數量級
- 用在網路通訊,資料庫儲存中恰到好處
- 預先支援所有基本型別,所有stl容器型別(除stack/queue之外)
- 支援變長 int32/uint32/int64/uint64
- 支援 stl::pair, boost::tuple
- 可選的版本控制,而非強制
- 對於小物件,通常不需要版本控制
- boost::serialization的版本號是強制的,當初我設計這個序列化框架就是因為boost不能省略版本號
- 非侵入式設計,不汙染名字空間
- 宣告式語法,簡單,可靠
- 等待你發現更多……
- 特別注意:vc6太古老,不符合C++規範,無法使用該框架
(二)該框架的易用性
還是用程式碼說明問題 (Talk is cheap. Show me the code. Torvalds, Linus (2000-08-25) ),先看這個例子:
struct MyData1 {
int a, b, c;
var_int32_t d; // d.t is int32 value var_uint64_t e; // e.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 = output.head(); // 淺複製,將 input 設為 output 已寫入的那部分 input >> d1 >> d2 >> d3; // 載入 //---------------------------------- // operator& 與operator<< 等效 output & d1 & d2 & d3; // 儲存
input = output.head(); // 淺複製,將 input 設為 output 已寫入的那部分
// operator& 與operator>> 等效 input & d1 & d2 & d3; // 載入}
模仿這段程式碼,可以完成大部分的現實需求,如果有更多的需求,可以使用該框架的高階功能。例如,系統中已經定義了一些資料結構,但又不能修改現有程式碼,怎樣給它們增加序列化能力呢?請看如下程式碼:
// in system header, can not changestruct SysData1 {
int a;
unsigned b;
string c;};// add these 2 function in your headertemplate<class DataIO>void DataIO_saveObject(DataIO& dio, const SysData1& x) {
dio & x.a & x.b & x.c;}template<class DataIO>void DataIO_loadObject(DataIO& dio, SysData1& x) {
dio & x.a & x.b & x.c;}// DataIO 新版中,更簡單的方法
DATA_IO_LOAD_SAVE_E(SysData2, &a &b &c)// #######################################################################// 如果現存的物件需要版本控制,參考如下程式碼:struct SysData2 {
int a;
unsigned b;
string c;};// add these 2 function in your headertemplate<class DataIO>void DataIO_saveObject(DataIO& dio, const SysData2& x) {
const unsigned curr_version = 2;
dio & serialize_version_t(curr_version);
dio & x.a & x.b;
dio & x.c;}template<class DataIO>void DataIO_loadObject(DataIO& dio, SysData2& x) {
const unsigned curr_version = 2;
serialize_version_t loaded_version;
in >> loaded_version;
if (loaded_version.t > curr_version) {
throw BadVersionException(loaded_version.t, curr_version, className);
}
dio & x.a & x.b;
if (loaded_version.t >= 2)
dio & x.c;}// DataIO 新版中,更簡單的方法:DATA_IO_LOAD_SAVE_EV(SysData2, &a &b& vmg.since(2, c))
(三)How It Works
DataIO_loadObject/DataIO_saveObject 只要在呼叫點可見,就可以對 SysData 進行序列化。因為 DataIO 序列化框架使用 DataIO_loadObject/DataIO_saveObject 來載入和儲存物件,這樣做的好處有以下幾點:
- 非侵入,物件型別和載入/儲存函式可以分離定義
- 否則無法為不可更改程式碼的物件增加序列化能力
- 這兩個函式可以定義在任何名字空間
- 根據C++的名字查詢規則,只要在呼叫環境和每個引數所在的名字空間中有相應的匹配函式,就會使用該函式。我們需要有效地利用這一點。
- DataIO_loadObject/DataIO_saveObject 這兩個函式名較長,並且罕見
- 因此不會與系統中的其他識別符號發生衝突。(對比boost::serialization中的serialize函式,它就比較容易和其他名字發生衝突,serialize 太常見了)。
(四)效能
在上面的程式碼中可以看到幾個陌生的名字: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 進行包裝,可以讓它象標準容器一樣使用,免除了複雜的編碼。後面我會繼續介紹。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70013015/viewspace-2924696/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- ToplingDB 的序列化框架:最佳化到極致框架
- Scrapy框架簡介框架
- HTML 框架簡介HTML框架
- Flask 框架簡介Flask框架
- Django框架簡介Django框架
- graphicsview框架簡介View框架
- Hibernate框架簡介⑤框架
- Hibernate框架簡介④框架
- Hibernate框架簡介③框架
- Hibernate框架簡介②框架
- Hibernate框架簡介①框架
- Spring框架簡介⑩Spring框架
- Spring框架簡介⑨Spring框架
- Spring框架簡介⑧Spring框架
- SpringMVC框架簡介②SpringMVC框架
- SpringMVC框架簡介①SpringMVC框架
- [轉]SSH框架簡介框架
- Spring框架簡介⑦Spring框架
- Spring框架簡介⑥Spring框架
- Spring框架簡介⑤Spring框架
- Spring框架簡介④Spring框架
- Spring框架簡介③Spring框架
- Spring框架簡介②Spring框架
- Spring框架簡介①Spring框架
- [原]ZolltyMVC框架簡介MVC框架
- 【SSH】--SSH框架簡介框架
- 簡易RPC框架:序列化機制RPC框架
- wsgiref模組、web框架、django框架簡介Web框架Django
- spring框架——Spring框架簡介Spring框架
- Flutter路由框架Fluro簡介Flutter路由框架
- uni-app 框架簡介APP框架
- 關於Struts框架簡介框架
- 前端框架的新星-Hyperapp 1.0簡介前端框架APP
- enzyme簡介——Airbnb的React測試框架AIReact框架
- Java集合框架系列教程一:集合框架簡介Java框架
- OSX 攻擊框架Empyre簡介框架
- 大資料框架原理簡介大資料框架
- 前端框架 Quasar-Framework 簡介前端框架Framework