您的位置:首页 > 理论基础 > 数据结构算法

可变长度数据结构

2009-09-27 17:12 369 查看
固定长度的数据结构很简单,大家每天都在用。

可变长度数据结构,都可以通过内嵌对象的形式,转化成固定长度的数据结构,大家每天也都在用,例如:

struct person
{
int    id;
string name;
string address;
};


每个 person 对象的长度是固定的,但是,其内嵌的 name 和 address 是变长的。从而,整个对象占据的总空间也是变长的。

但是,将这样的的对象平坦化,使之只占据一块连续空间,使用的人很少,因为在绝大多数情况下,很少有人思考这个问题,并且,大多数问题已经使用内嵌数据结构解决掉了。

然而,如果内存很紧张,或者需要处理得数据量非常大,这种方式浪费的内存就太多了。假定我们现在创建了一个 map<int, person> ,在gcc4.1 的 64位环境中,按8字节对齐,这个 map 总共会占用多少内存呢?

sizeof(map) = 48

sizeof(string) = 8

sizeof(person) = 24

sizeof(person_node) = alignto(24 + 32(rbtree node overhead), 8) = 56

sizeof(string_node) = 8*3(refcount, size_endptr, capacity_endptr)

memsizeof(string) = sizeof(string_node) + alignto(strlen + 1, 8)

memsizeof(person_node) = sizeof(person_node) + memsizeof(name) + memsizeof(address)

如果 avg(name) = 10, avg(address) = 20

实际占用的空间,大约还要再加上4:avg(name) = 14, avg(address) = 24

那么 memsizeof(person_node) = 56 + 24*2 + 14 + 24 = 142

那么该map 实际占用空间(gcc 的每个 map 还有一个虚结点:32个字节):

48+32+n*142 = 80+n*142

如果使用一种理想的变长数据结构,再加上红黑树的优化(none virtual node, compressed color, no parentptr),需要多少内存呢?

sizeof(map) = 16

memsizeof(person_node) = alignto(16(treenode) + 4(id) + 2*1(strlen) + 10(name) + 20(address) = 52, 8) = 56

memsizeof(map) = 16 + 56*n

内存一下节省了 60% 还多,也就是说,如果内存大小已经固定,可以装入 2.5 倍的数据。

如果用来做集群缓存,可以节省50%的机器(系统也要占一些内存)。

如果有5.6G的内存可用,就可以装入1亿条数据。

并且,在节省了60%内存的同时,还有另外一种好处:提高cache命中率,如果3个字段访问的频率相同,cpu的 cache miss 会降低3倍。

可以预见,因为cache miss降低,map.find 速度会大幅提高。

这个可变数据结构可以这样设计:

struct person2
{
int id;
unsigned char nName, nAddress;
char data[1];

// not terminated with '/0'
const char* getName()    const { return data; }
const char* getAddress() const { return data + nName; }
int getSize() const { return offsetof(person, data) + nName + nAddress; }
};


更复杂的情况:

如果nName 和 nAddress 要大于 255 呢?——把 unsigned char 改成 unsigned short, 甚至 unsigned int

如果person的字段很多,例如有20个字段:

struct person3
{
int id;
string name;
string address;
string ex1;
string ex2;
string ex3;
string ex4;
string ex5;
//  more...

//  var data structure:
//  unsigned char nName, nAddress, nEx1, nEx2;
//  const char* getEx1() const { return data + nName + nAddress; }
//  const char* getEx2() const { return data + nName + nAddress + nEx1; }
//  const char* getEx3() const { return data + nName + nAddress + nEx1 + nEx2; }
//  const char* getEx4() const { return data + nName + nAddress + nEx1 + nEx2 + nEx3; }
//  const char* getEx5() const { return data + nName + nAddress + nEx1 + nEx2 + nEx3 + nEx4; }
//  more...
};


这就不光是写代码的复杂了,运行时字段访问的性能也成问题!性能问题可以用另外一种方式——使用直接定位——来解决。

struct person4
{
int id;
string name;
string address;
string ex1;
string ex2;
string ex3;
string ex4;
string ex5;
//  more...

//  var data structure:
//  unsigned short pAddress, pEx1, pEx2, pEx3, pEx4, pEor;
//  char data[1];
//  const char* getName() const { return data + 0; }
//  const char* getAddress() const { return data + pAddress; }
//  const char* getEx1() const { return data + pEx1; }
//  const char* getEx2() const { return data + pEx2; }
//  const char* getEx3() const { return data + pEx3; }
//  const char* getEx4() const { return data + pEx4; }
//  const char* getEx5() const { return data + pEx5; }
//  more...
//  int sizeEx1() const { return pEx2 - pEx1; }
//  int sizeEx2() const { return pEx3 - pEx2; }
//  int sizeEx3() const { return pEx4 - pEx3; }
//  int sizeEx4() const { return pEx5 - pEx4; }
//  int sizeEx5() const { return pEor - pEx5; }
};


如果需要变长数据的数组,怎么办?简单,offset array + data byte array,具体实现方式与 person4 类似,只不过 offset array 元素需要使用更宽的整数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: