前言
在实现DDS的过程中需要一个bitmap来快速查找某个序列号的message状态,这里参考fastdds中的bitmap实现,在此分析并记录一下。
关键词:bitmap,__builtin_clz
需求
- 能够接收自定义的sequence类型
- 具有动态范围
实现
首先BitmapRange使用了三个模板:
template<class T, class Diff = DiffFunction<T>, uint32_t NBITS = 256>
class BitmapRange
{};
模板T可以是自定义的序列号,要求T实现“>=”运算符且能够与uint32_t进行加法运算;
Diff是针对类型T实现的仿函数,用于计算两个T之间的差距;
NBITS指定Bitmap的最大范围,默认是256个元素;
Diff的默认仿函数实现如下:
template <class T>
struct DiffFunction
{
constexpr auto operator () (T a, T b) const -> decltype(a - b)
{
return a - b;
}
};
BitmapRange采用uint32_t作为一个单元,一个单元可以容纳32个元素,所需要的单元数量根据最大范围NBITS进行计算:
#define NITEMS ((NBITS + 31u) / 32u) //bitmap需要多少个uint32_t
这些uint32_t单元储存在一个array中,成员变量的名字是bitmap_:
using bitmap_type = std::array<uint32_t, NITEMS>;
...
bitmap_type bitmap_;
BitmapRange的动态范围通过一个base_和range_max_实现,所有的元素i必须满足 base_<= i <=range_max_;
BitmapRange的主要接口有:
BitmapRange();
explicit BitmapRange(T base);
void base_update( T base);
T max();
T min();
bool is_set(const T& item);
bool add(const T& item);
BitmapRange()
默认构造函数,构造一个空的bitmap,base设置为0。
BitmapRange() noexcept
: base_()
, range_max_(base_ + (NBITS - 1))
, bitmap_()
, num_bits_(0u)
{
}
BitmapRange(T base)
带base的构造函数,和默认构造函数类似,只不过会设置base成传入的值。
void base_update( T base)
移动bitmap窗口,保留原有的数据
void base_update(T base) noexcept
{
if (base == base_){return;}
Diff d_func; //用此函数计算新的base和当前base_的差距
if (base > base_)
{
// bitmap所有内容左移n_bits位
uint32_t n_bits = d_func(base, base_);
shift_map_left(n_bits);
}
else
{
// bitmap所有内容右移n_bits位
uint32_t n_bits = d_func(base_, base);
shift_map_right(n_bits);
}
base_ = base;
range_max_ = base_ + (NBITS - 1);
}
这里介绍下左移操作。
void shift_map_left(uint32_t n_bits)
{
if (n_bits >= num_bits_)
{
// 移动位数超过当前最高有效位,全部清零
num_bits_ = 0;
bitmap_.fill(0UL);
}
else
{
num_bits_ -= n_bits;// 更新最高有效位数量,减去左移距离
uint32_t n_items = n_bits >> 5; // 移动位多少个完整单元
n_bits &= 31UL; // 模32
if (n_bits == 0)
{
// 左移刚好32整数倍,n_items个单元整体前移,后面补零
// aaaaaaaa bbbbbbbb cccccccc dddddddd ->block0
// eeeeeeee ffffffff gggggggg hhhhhhhh ->block1
// iiiiiiii jjjjjjjj kkk00000 00000000 //82
// 00000000 00000000 00000000 00000000 ->block3
// shift 32 bits
// eeeeeeee ffffffff gggggggg hhhhhhhh ->block0
// iiiiiiii jjjjjjjj kkk00000 00000000 //50
// 00000000 00000000 00000000 00000000 ->block2
// 00000000 00000000 00000000 00000000 ->block3
std::copy(bitmap_.begin() + n_items, bitmap_.end(), bitmap_.begin());
std::fill_n(bitmap_.rbegin(), n_items, 0);
}
else
{
// aaaaaaaa bbbbbbbb cccccccc dddddddd ->block0
// eeeeeeee ffffffff gggggggg hhhhhhhh ->block1
// iiiiiiii jjjjjjjj kkkkkkkk llllllll ->block2
// mmmmmmmm nnnnnnnn oooooooo ppp00000 ->block3
// shift 45 bits,1 block, mod 13
// fffggggg ggghhhhh hhhiiiii iiijjjjj ->block0
// jjjkkkkk kkklllll lllmmmmm mmmnnnnn ->block1
// nnnooooo oooppp00 00000000 00000000 ->block2
// 00000000 00000000 00000000 00000000 ->block3
uint32_t overflow_bits = 32u - n_bits;
size_t last_index = NITEMS - 1u;
// 非完整单元向前移动,覆盖需要左移的完整单元,非完整移动的单元从第n_items块开始
for (size_t i = 0, n = n_items; n < last_index; i++, n++)
{
// 第n块左移n_bits位,剩余的用n+1块高位前(32-n_bits)补上
bitmap_[i] = (bitmap_[n] << n_bits) | (bitmap_[n + 1] >> overflow_bits);
}
// 最后一块只用左移n_bits,后面的没得补了
bitmap_[last_index - n_items] = bitmap_[last_index] << n_bits;
// 尾部的n_items个单元补零
std::fill_n(bitmap_.rbegin(), n_items, 0);
}
}
}
T max()
返回当前bitmap里面被设置成1的最高位,根据当前base和最高位num_bits_计算得到。
T max() const noexcept
{
return base_ + (num_bits_ - 1);
}
T min()
返回当前bitmap里面被设置成1的最低位,这里需要遍历每个单元,寻找第一个非零单元的第一个非零bit位。
T min() const noexcept
{
T item = base_; //item从base开始,每遍历了一个单元,item就偏移一个单元的长度,即32位
uint32_t n_longs = (num_bits_ + 31u) / 32u; //需要遍历的单元数量
for (uint32_t i = 0; i < n_longs; i++)
{
uint32_t bits = bitmap_[i];
if (bits) //这个单元不是0,那么寻找第一个非零位
{
//使用gcc内置函数__builtin_clz来统计uint32_t高位有几个连续的0;
uint32_t offset = static_cast<uint32_t>(__builtin_clz(bits));
//最低位就是当前单元起始位加上高位0的数目
return item + offset;
}
item = item + 32u; //item偏移一个单元的长度,即32位
}
return base_;
}
bool is_set(const T& item)
判断item在bitmap中的状态
bool is_set(const T& item) const noexcept
{
//确保item在范围内
if ((item >= base_) && (range_max_ >= item))
{
Diff d_func;
uint32_t diff = d_func(item, base_); //计算item和base的偏移量
if (diff < num_bits_) //如果超出了当前已设置位1的最高位,那肯定返回false
{
uint32_t pos = diff >> 5; //右移5位,得到所在单元的编号
diff &= 31UL; //取32的模,得到高位向低位的偏移量
return (bitmap_[pos] & (1UL << (31UL - diff))) != 0; //1左移到目标位,然后和bitmap单元与运算
}
}
return false;
}
bool add(const T& item)
向bitmap中添加item
bool add(const T& item) noexcept
{
//与is_set类似
if ((item >= base_) && (range_max_ >= item))
{
Diff d_func;
uint32_t diff = d_func(item, base_);
num_bits_ = std::max(diff + 1, num_bits_); //更新最高设置位
uint32_t pos = diff >> 5;
diff &= 31u;
bitmap_[pos] |= (1u << (31u - diff)); //1左移并与bitmap单元或运算
return true;
}
return false;
}