B+树性能测试(C++实现)

B+树

简介

文章主要是对我自己实现的B+树的各项指标测试结果展示。B+树的CRUD具体算法文本未涉及,后续可能会补充。

项目地址

github.com/SirLYC/BPTr…

B+树简介

引自维基百科

B+ 树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。

B+树结构

B+树有一个重要的参数叫(m),决定了一颗B+树每一个节点存储关键子的个数。

每一个节点都会按顺序存储一组关键字,对于非根节点,其关键字树s >= (m + 1) /2。对于叶子节点,其结构中存储指向值的指针,与关键字对应,同时还有一个next指针,指向下一个兄弟叶子节点,因此找到最左叶子节点后可以按关键字顺序遍历;对于非叶子节点,存有s个指向子节点的指针。

B+树通过插入时分裂,删除时向兄弟节点借关键字或合并兄弟节点实现平衡,所有的叶子节点都在同一层。查询、插入、删除效率都是Log(N)

实现的public API

template<typename K, typename V>
class BPTree {
private:
    ...

public:
    // constructor and destructors
    ...

    /**
     * deserialize from a file
     */
    static BPTree<K, V> deserialize(const std::string &path);

    static BPTree<K, V> deserialize(const std::string &path, comparator<K> comp);

    void put(const K &key, const V &value);

    void remove(K &key);

    /**
     * @return NULL if not exists else a pointer to the value
     */
    V *get(const K &key);

    bool containsKey(const K &key);

    int getOrder();

    int getSize();

    /**
     * iterate order by key
     * @param func call func(key, value) for each. func returns true means iteration ends
     */
    void foreach(biApply<K, V> func);

    void foreachReverse(biApply<K, V> func);

    void foreachIndex(biApplyIndex<K, V> func);

    void foreachIndexReverse(biApplyIndex<K, V> func);

    void serialize(std::string &path);

    /**
     * clear the tree
     * note that all values allocated will be freed
     */
    void clear();
};
复制代码

Tips:为了兼容自定义类别,需要传入比较大小的函数指针,或实现相应的>、=、<等运算符;

重要数据结构

Node:B+树的索引节点

主要数据结构如下:

struct Node {
    // parent
    // if root, parentPtr == NULL
    Node *parentPtr = NULL;
    // flag
    bool leaf;
    List<K> keys;
    /*-------leaf--------*/
    Node *previous = NULL;
    Node *next = NULL;
    List<V> values;
    /*-------index-------*/
    List<Node *> childNodePtrs;
    // for init
    int initCap;
    // constructor
    ...
};
复制代码

List<T>:使用定长数组实现的List,比起std::vector<T>功能更简单,效率更高;内存会在移除一定数量元素后减小。

序列化

  • 文件后缀为bpt
  • 头部格式:
偏移(byte)大小(byte)内容
04LYC\0 头部标识
44order,int类型,B+树的阶
84initCap,int类型,每个节点预分配大小
124size,int类型,元素个数
  • 如果size不为0,头部结束后就是根节点,节点有同一的格式,节点前面通用格式:
偏移(相对于节点起始,byte)大小(byte)内容
04leaf,int类型,标识节点是否为叶子节点
44sizeofK,int类型,key类型占字节数
84kSize,int类型,该节点拥有的关键字数量
12kSize * sizeofK按顺序存储关键字
  • 对于叶子节点
偏移(相对于节点起始,byte)大小(byte)内容
12 + kSize * sizeofK4sizeofV,int类型,value类型占字节数
16 + kSize * sizeofKkSize*sizeofK按顺序存储值
  • 对于非叶子节点
偏移(相对于节点起始,byte)大小(byte)内容
12 + kSize * sizeofKkSize * 8long类型,按顺序存储字节点在文件中的偏移

实现要点

  • 最初的实现是使用vector,测下来性能不是特别理想;
  • 基于节点内关键字有序的特点,查找时使用二分查找;
  • 存储子节点应该存储指向节点的指针。因为涉及分裂、合并操作,需要复制列表,如果存储的是结构,复制会造成递归复制,效率低,且不易控制内存;
  • 因为root节点没有最少关键字限制,在删除节点操作完成后,需要检查一下root子节点数量,如果为1,直接将root的字节点设置为root,否则删除子节点后可能会造成root的关键字、子节点丢失。
  • 每一次插入、删除节点最后一个关键字后需要向上更新parent。
  • 分裂、合并操作时对于叶子节点next和previous指针要更新。

测试

测试环境:

文件main.cpp有如下宏,1表示开启测试:

// 测试List性能(和vector对比)
#define TEST_LIST 0
// 测试B+树功能正确性
#define TEST_FUNC 0
// 测试B+树的速度(增删查改)
#define TEST_SPEED 0
// 测试B+树的堆使用及内存泄漏(build后使用工具测试)
#define TEST_MEM 0
// 测试B+树的序列化与反序列化
#define TEST_SERIAL 0
复制代码

List测试

  • 数据量:10^5
  • 增、删数据,断言对应位置是否如预期(功能测试)
  • 尾部插入测试(预分配和不预分配)
  • 头部插入测试
  • 头删除测试
  • 尾删除测试
  • rangeRemove测试

测试运行结果:

表格:

List(ms)vector(ms)
尾部插入1.5064.724
尾部插入(预分配空间)1.2012.765
头部插入834.804906.981
移除一半元素(头部)619.493805.379
移除一半元素(尾部)1.4447.523
rangeRemove(一半)0.0650.558

柱状图:

B+树功能测试

  • 数据量:10^5
  • 数据插入后断言插入数据存在
  • 去除一半数据,断言去除数据不存在,未去除数据存在
  • 重新插入所有数据,断言所有数据都存在(测试删除是否破坏结构)
  • clear()后断言之前数据都不存在
  • 插入全部数据后,测试遍历方法(key顺序测试)

速度测试

  • 数据量:10^8、10^7、10^6
  • B+树阶为 log(TEST_SIZE)^2
  • 循环插入数据
  • 循环访问所有数据
  • 循环移除数据

测试运行结果:

表格:

bp tree(ms)stl map(ms)
插入(10^8)192808.064325621.333
访问(10^8)163102.022280150.403
移除(10^8)213982.406366576.836
插入(10^7)11825.82122213.139
访问(10^7)10190.87018137.073
移除(10^7)15130.01522133.154
插入(10^6)1057.2911624.615
访问(10^6)888.1861155.504
移除(10^6)1099.5841495.433

柱状图:

内存测试

  • 数据量:10^6
  • 添加数据后移除数据
  • 循环5次,添加后调用clear()
  • 工具:Xcode Instruments

测试运行结果:

序列化测试

  • 数据量:10^6~0

测试运行结果:

表格:

数据量序列化(ms)反序列化(ms)文件大小(bytes)
013.7940.08216
10.150.0340
100.1110.033112
10^20.2070.0641052
10^30.8770.49010168
10^47.8705.567101312
10^561.31859.4181012108
10^6694.879477.63010127572

转载于:https://juejin.im/post/5cf27fa06fb9a07f070e14c2

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值