文件位于 llvm/include/llvm/[[ADT]]/SmallPtrSet.h算法
文件注释:'Normally small' pointer set -- 通常小型指针集合。数组
== 定义的类 ==
* RoundUpToPowerOfTwo -- N 取整到 2 的幂次。
* RoundUpToPowerOfTwoH -- 辅助 N 取整到 2 的幂次。
* [[SmallPtrSetImpl]]
* [[SmallPtrSetIteratorImpl]]
* [[SmallPtrSetIterator]]
* [[SmallPtrSet]]函数
== 辅助模板 RoundUpToPowerOfTwoH ==
辅助模板 RoundUpToPowerOfTwoH<N, isPowerTwo> - 若是 N 不是 2 的幂次,则增长它。这个模板类用于辅助实现 [[RoundUpToPowerOfTwo]]。性能
类概要:
<syntaxhighlight lang="cpp">
template<unsigned N, bool isPowerTwo> struct RoundUpToPowerOfTwoH {
enum { Val = N }; // 普通版本,class::Val == N
};学习
template<unsigned N> struct RoundUpToPowerOfTwoH<N, false> {
// 特化版本,class::Val == N,是最小的大于 N 的 2 的幂次。
enum {
// N|(N-1) 设置右边的 0 比特位为 1,如 0b00101100 -> 0b00101111。
// We could just use NextVal = N+1, but this converges faster. N|(N-1) sets
// the right-most zero bits to one all at once, e.g. 0b0011000 -> 0b0011111.
Val = RoundUpToPowerOfTwo<(N|(N-1)) + 1>::Val // 注意:配合 RoundUpToPowerOfTwo
};
};
</syntaxhighlight>优化
若是 N 里面 1010 中间带 0 比较多,可能要递归展开几回 RoundUpToPowerOfTwo,可否有更好的办法呢?this
== 辅助模板类 RoundUpToPowerOfTwo ==
RoundUpToPowerOfTwo - 辅助模板类,用于求取最小的大于等于 N 的 2 的幂次。若是 N 已是 2 的幂次,则值既是 N。指针
类概要:
<syntaxhighlight lang="cpp">
template<unsigned N>
struct RoundUpToPowerOfTwo {
static bool isPowerTwo = (N&(N-1)) == 0}; // 判断 N 是否已是2的幂次。
enum { Val = RoundUpToPowerOfTwoH<N, isPowerTwo>::Val }; // 取整到 2 的幂次。
}
</syntaxhighlight>orm
为了看起来易懂,我添加了 bool isPowerTwo,其中 N&(N-1) 是典型的判断是不是 2 的幂次的方法。对象
== 类 SmallPtrSetImpl ==
类 SmallPtrSetImpl 提供给 SmallPtrSet<> 模板类做为公共基类。SmallPtrSet 有两种模式,small 和 large 集合。相似于 [[SmallVector]], [[SmallString]] 等 Small 系列容器。
SmallPtrSet 里面有在对象内的指针数组,在 small 模式下,指针加入到这个数组中。若是这个数组不够用了,则自动增加到 large 模式。于是,当集合一般比较小的时候,应该用 SmallSet,此时不进行额外的内存分配。
large 模式下使用典型的指数探测(exponentially-probed)哈希表。空桶以非法指针值(-1)表示,这样容许插入 null 指针。墓碑(tombstone)以另外一个非法指针值(-2)表示,以容许从集合中删除。哈希表装载因子达到 3/4(0.75) 的时候将自动扩充,表的大小加倍。
类概要:
<syntaxhighlight lang="cpp">
class SmallPtrSetImpl {
PTR SmallArray[]; // 保存指针类型PTR 的数组,small 模式下用。
PTR CurArray[]; // 当前使用的指针数组,若是 == SmallArray,则表示在 small 模式下。
unsigned CurArraySize; // CurArray 的大小,取值是 2 的幂次。
unsigned NumElements; // 已在集合中的元素数量。
unsigned NumTombstones;// 被删除的元素数量。
this(), ~this() // 带参数的构造、析构。protected 只能被派生类使用。
empty(),size(),clear() 容器方法
emptyMarker -- (void*)-1 使用 -1 做为空槽标记。
tombstoneMarker -- (void*)-2 使用 -2 做为墓碑(被删除元素)标记。
hash(), grow(), find(), ... // 不少给派生类使用的实现辅助函数
}
</syntaxhighlight>
类中各个数据、函数的使用放在派生类 SmallPtrSet 中说明。
== 类 SmallPtrSetIteratorImpl ==
SmallPtrSetIteratorImpl - 做为模板 SmallPtrSetIterator<> 的公共基类。
类概要以下:
<syntaxhighlight lang="cpp">
class SmallPtrSetIteratorImpl {
void **Bucket; // 指向 hashtable 桶的指针。为清晰,去掉了 const 修饰符。
this(), ==, !=, ++ 的实现,由派生类使用。
}
</syntaxhighlight>
== 类 SmallPtrSetIterator ==
为 SmallPtrSet 实现 const_iterator,类概要以下:
<syntaxhighlight lang="cpp">
template<typename PtrTy> class SmallPtrSetIterator
: public SmallPtrSetIteratorImpl { // 派生自...
value_type, reference, pointer 等标准容器类型定义
this(), *, ++ 迭代器的实现。这是一个仅向前迭代器(forward_iterator_tag)。
}
</syntaxhighlight>
== 类 SmallPtrSet ==
模板类 SmallPtrSet 实现为存储少许元素而进行优化的集合。内部将对 SmallSize 参数取整到 2 的幂次。
类概要:
<syntaxhighlight lang="cpp">
template<class PtrType, unsigned SmallSize> // 参数:指针类型,元素数量
class SmallPtrSet : public SmallPtrSetImpl {
enum SmallSizePowTwo // 对 SmallSize 取整到 2 的幂次。用 RoundUpToPowerOfTwo 实现
void *SmallStorage[SmallSizePowTwo+1]; // in-object 指针存储。
this() // 普通构造与复制构造,等等。
begin(),end(),insert(),erase(),count() 等标准容器方法实现。
... 其它略
}
</syntaxhighlight>
== SmallSetPtr 实现机理 ==
SmallSetPtr 的几个主要函数实际都实如今 SmallSetPtrImpl 中,也即在 SmallSetPtr.cpp 文件中实现。主要学习 insert, erase, find 等核心的方法。
=== insert() ===
insert(ptr) -- 用于向集合中插入新元素 ptr
前述提到有两种模式,small 和 large。插入的过程以下:
* 1. 判断是否 small 模式?依据是 CurArray 指向的是内部 in-object 的那个 SmallStorage 数组。
* 2. 在 small 模式下,线性扫描 SmallStorage[] 以查找 ptr 是否已经存在。若是存在则返回 false 表示插入失败(或不须要插入)
* 3. 若是没找到,而且 SmallStorage[] 还有空间,则放到 SmallStorage[] 末尾,返回 true。
* 4. SmallStorage[] 没有空间了,转到 large 模式。
* 5. large 模式,判断装载因子(load factor) > 3/4 吗?若是是,则 grow() 见注1.
* 6. 若是墓碑过多(空位置少于1/8),则 rehash。注2.
* 7. 为 ptr find_bucket(),若是找到的位置上已是 ptr 了表示前面插入过了,则返回 false.
* 8. 否之该位置能够插入,在该位置插入,更新数据并返回 true。
关于为什么使用 empty, tombstone 参见后面 erase(), find_bucket() 的说明。
* 注1:grow() SmallPtrSet 使用的是使用[[开放寻址法]]实现的 hash 表,所以不能有太大的装载因子,不然致使性能急剧降低。3/4(0.75) 是一个较好的时间-空间平衡的装载因子。
* 注2:rehash -- 删除的元素被标记为墓碑(tombstone),若是墓碑过多将影响搜索效率,甚至致使无限循环搜索空位置。所以当墓碑标记过多的时候,须要从新 hash() 现有元素填放到正确的位置以消除墓碑。若是 grow() 增长了新空间,则会自动进行 hash() 同时消除了墓碑,所以那种状况不须要 rehash()。
=== erase() ===
erase(ptr) 用于从集合中删除指定元素。
过程以下:
* 1. 若是是 small 模式,线性查找 SmallStorage[]。找到则删除,返回true;没找到则返回 false。
* 2. 为 ptr find_bucket()。若是没找到,则返回 false.
* 3. 找到了,则标记这个 bucket 位置为墓碑(tombstone)表示元素被删除了,返回 true。
=== find_bucket() ===
find_bucket(ptr) 为 ptr 查找所在桶的位置,若是没有则返回可插入的空桶的位置。
find_bucket() 仅查找 large 模式下的 CurArray,这是一个开放寻址法实现的 hash 表。
* 1. 计算 ptr 的 hash 值。这里算法使用 hash & (桶数量-1)。由于桶数量(CurArraySize) 被要求是 2 的幂次,所以可使用 & 操做。
* 2. 循环:
* 3. 若是在 hash 的桶的就是 ptr 则找到了,返回这个桶位置。
* 4. 若是 hash 的桶是 empty,表示找到一个空位置,没有可能有重复的 ptr 在集合中,返回一个可用的桶位置。所谓可用,指要么用这个 empty 桶位置,要么前面有发现一个被删除的元素位置(tombstone),则返回被删除位置的桶。
* 5. 若是 hash 的桶标记为 tombstone,则表示找到一个被删除元素的位置。和 ptr 元素冲突的可能元素依旧可能存在,须要继续查找,但记录下这个 tombstone 的位置,其可能在步骤 4 返回。
* 6. hash 值加上 probe_amt++,继续探测。按照这里的算法,应该是[[二次探测再散列]]。
墓碑(tombstone)用来标记被删除的元素,若是没有这种 tombstone 标记,几个冲突的元素若是插入集合中,而前面的被删除,则后面的就将探测不到了。
后面要学习的 DensyMap 也是用的二次探测法实现的。