研究生只念一年的坏处就是毕业设计好像变成一年一度的了……这次毕设是和并行计算有关(毕竟念的是「高性能计算硕士」),多线程是不够的,因为一台设备的CPU核心数毕竟有限,所以多进程的并行计算才能发挥计算机集群(HPC cluster)的计算威力。这方面的de facto standard就是MPI了,而在C++项目中可以通过Boost库的MPI包装更方便、「更C++」的来调用。Boost库的质量和重要程度个人感觉仅次于STL了,看看C++11吸收了大量Boost库进入STL就知道Boost有多厉害。
和Serialization的关系?既然用C++,就免不了自定义类吧,想要把一个类的实例通过MPI发送到其他MPI节点上,首先就要把类进行serialize,然后把serialized memory发送出去,接收方再unpack还原成一个实例。就不展开说了,简而言之这一点和MPI的通信原理有关。
所以Serialization库的用途就是可以帮助把类(包括模板类)进行serialize,这样处理之后的类就能使用Boost库中其他的函数比如archive库来把类的实例进行输入、输出(想想游戏的读档、存档)。
简单示例
以我目前还在进行的毕业设计代码文件position.h为例:
#ifndef POSITION_H #define POSITION_H #include <array> #include <boost/serialization/access.hpp> #include <boost/mpi/datatype.hpp> class position { public: position(int _file = 0, int _rank = 0); position(const position &b); int file; int rank; bool not_in_range(int min_file, int max_file, int min_rank, int max_rank) const; position& operator= (const position &b); position operator+ (const position &b); position operator- (const position &b); bool operator< (const position &b) const; private: friend class boost::serialization::access; template<class Archive> void serialize(Archive &ar, const unsigned int version) { ar & file; ar & rank; } }; inline bool operator !=(const position &a, const position &b) { return (a.file != b.file) || (a.rank != b.rank); } inline bool operator ==(const position &a, const position &b) { return !(a != b); } typedef std::array<position, 2> pos_move;//moving a piece from move[0] to move[1] template<class Archive> void serialize(Archive &ar, pos_move &p, const unsigned int version) { ar & p[0]; ar & p[1]; } BOOST_IS_MPI_DATATYPE(position) BOOST_IS_MPI_DATATYPE(pos_move) #endif //POSITION_H
这个类是非常简单,只包含了基础数据类型(int, float, double, bool等,本例只有int)。把一个类进行serialize适配其实就是添加serialize这个特殊的函数,而为了让Archive能够访问私有成员,需要加上 friend class boost::serialization::access;
这一句。然后在private域写好 template void serialize(Archive &ar, const unsigned int version)
函数。操作符&相当于流操作符<<和>>的合体,在输出数据的时候就是<<,在输入的时候就是>>。
你也可以把serialize函数拆开,拆开写就不是serialize函数了,而是save和load两个函数,可以更加灵活地控制数据输入输出的操作。
最下面的pos_move是一个array的模板类,STL的类库当然不会加上Boost的serialize的支持,所以我们要手动适配。因为我们不能修改STL的头文件(呃,理论上可以,但是那样的话你就要ship一份你改过的标准库,何必呢?),所以适配函数是「非侵入式」的,也就是定义在类外面的。可以看到函数的prototype变长了,第二个参数是要serialize的类的引用,操作倒是基本一样的。
宏BOOST_IS_MPI_DATATYPE是告诉Boost这个类型的数据长度是固定的(没有用到动态长度的容器如list, vector等),Boost::MPI会对这样的类进行一些性能上的优化,以后有机会谈Boost::MPI的时候再细说。
衍生类
假设我以position为基类衍生了一个global_position类,那么衍生类也需要做类似的操作,适配好serialize函数,不过不同的地方在于serialize函数体:
private: int offset; friend class boost::serialization::access; template<class Archive> void serialize(Archive &ar, const unsigned int version) { ar & boost::serialization::base_object<position>(*this); ar & offset; }
首行是让Boost调用基类的serialize函数,而offset是global_position唯一新增成员数据,所以需要显式地处理一下。
另外一个容易踩进去的坑是忘记register或export,然后使用了基类指针,忘记的结果是编译时通过,运行时当需要serialize基类指针的时候抛出异常。这个有点冗长,就不在这里说了,可以看看官方文档的说明。如果你压根就不使用指针,就不用担心这个问题了(但是不用指针如何实现多态呢?不需要多态的话,类的继承衍生又没很大用处了)。
STL容器
如果使用了STL的容器,比如list、vector等,只需要包含对应的serialization头文件即可,并不需要做特别地处理。如:
private: std::list<position> pos_list; friend class boost::serialization::access; template<class Archive> void serialize(Archive &ar, const unsigned int version) { ar & pos_lis; }
你只需要包含好头文件#include <boost/serialization/list.hpp>
即可。
参考文章: Boost::Serialization
2 responses to “也谈Boost::Serialization的用途和用法”
量子化 量子化!
atomic应该尽量用STL,C++11的标准要求了很多东西,Boost放宽了,但是Boost的atomic没有STL的强大。过段时间我应该会写一篇关于std::atomic的文章,毕竟这也是个折腾了我几天的部分。