LLVM 数据结构简介
LLVM 数据结构简介
- LLVM 数据结构简介
- ArrayRef
- 特点
- 与标准库 array 对比
- SmallVector
- 与标准库 vector 的对比
LLVM 数据结构简介
ArrayRef
llvm::ArrayRef 是一个轻量级的只读容器,主要用于引用一段连续的内存区域。它的设计目标是提供高效的数据访问,而不需要拥有底层数据的所有权。这使得 ArrayRef 特别适合在函数参数中按值传递,从而避免了不必要的内存拷贝。
特点
- 只读:ArrayRef 不能修改其引用的数据,也不能添加新元素(另一个容器 MutableArrayRef 可以修改)。
- 轻量级:它只存储一个指向数据的指针和数据的长度,而不存储实际的数据,所以拷贝时非常高效。
- 按值传递:在传递 ArrayRef 时,实际上传递的是一个指针和其指向数据的长度,所以不需要再对其按引用传递。
- 操作一致:它的大多数操作,与 STL array 保持一致。
与标准库 array 对比
- 所有权:llvm::ArrayRef 不拥有其引用数据的所有权,只是对数据的引用;std::array 拥有数据的所有权,存储在栈上。
- 大小:llvm::ArrayRef 容量是动态的,可以引用任意长度的数组。但由于数组长度是静态的,所以从程序角度看,ArrayRef 的具体引用类型,容量是确定的;std::array 大小在编译期间固定,和 C 数组一样。
- 可变性:llvm::ArrayRef 是只读的,不能修改引用数据;std::array 允许修改其元素,提供完整的读写权限。
- 初始化:llvm::ArrayRef 的初始化更灵活,可以从 C 数组、std::array,std::vector 或其他顺序容器初始化。std::array 只能从初始化列表或在定义时使用构造函数初始化。
- 性能:llvm::ArrayRef 适合在高频传递参数时使用。std::array 默认按值拷贝,会带来开销,需要指定其引用类型作为参数类型。如果是 constexpr 修饰,编译器可以优化传参性能。
template <typename T>class ArrayRef {
private:const T* data = nullptr;size_t length = 0;
}
SmallVector
SmallVector是llvm中自定义的一种通用数据结构,在llvm的各层次间都可以使用。SmallVector与std::Vector非常类似 ,支持迭代、push_back、pop_back,以及随机存取元素。
SmallVector对与元素较少的情况时性能是优与std::vector的。 这是因为SmallVector使用了一种比较通用的局部缓存设计模式,减少了malloc/free的巨大开销。
std::vector会调用malloc函数申请一块内存用于放置元素,std::array是对内置数组的一个封装,因此其存放元素的数组会与std::array放置在同一个位置。如果std::array是在栈上声明的,那么其存放元素的数组也位于栈上。
内存的位置不同导致了std::array与std::vector效率的不同。因为std::vector是通过malloc申请的堆内存,而std::array是栈内存。
堆内存的申请需要调用系统函数分配内存,这个开销是巨大的。相比之下,栈内存几乎是零开销,因为值需要调整栈指针。当然std::array也并不总是在栈上的,取决与你分配它的方式。但是任然会比std::vector少分配一次堆内存。
smallvector的本质就是当元素个数少的时候像std::array一样将存储元素的内存放在类里面,当元素个数多的时候像std::vector一样分配堆内存。这样就兼具了两者的优点。 这种操作被称为局部缓存设计模式,在llvm很多地方都有体现。
template<typename T, unsigned N = CalculateSmallVectorDefaultInlinedElements<T>::value>
class SmallVector;
llvm::SmallVector 是一个可变长数组,类似于 std::vector,同时它对较小长度的数组做了优化。它的内存管理方式采用局部缓存的设计思路,在对象内部预留一小块空间,用于存储数据。当数据量超出预留空间的大小时,才会将数据放在堆上。它本身保存一部分元素,这便使得在小数组中,避免进行堆分配的操作,提高了效率。
注意到,它带有一个含默认值的模板参数 N,它用来指定预留空间的大小,默认不指定时,编译器会自动选择一个合理的阈值(通常考虑依据是栈空间的占用情况)。
与标准库 vector 的对比
- 性能优势:在数据量较小时,由于不会涉及到堆内存分配和管理的开销,性能会优于 std::vector。另外,它可以识别平凡可复制的特性,从而更细粒度地做内存管理。
- 退化时的性能损失:当发生从栈到堆的退化时,llvm::SmallVector 会带来数据拷贝的开销。所以需要仔细考虑阈值的设定。
- 可能浪费空间:llvm::SmallVector 会在定义时预分配设定的 N 的空间,如果实际数据少于 N,那么会存在空间浪费的问题。std::vector 也存在类似问题。
SmallVector,它继承了SmallVectorImpl和SmallVectorStorage。SmallVector本身只有一系列构造函数(拷贝构造、赋值构造等),没有成员变量,具体的一些成员函数放在SmallVectorImpl中,存储元素的数组放在SmallVectorStorage中。 SmallVectorStorage类中直接声明了一个数组。
/// Storage for the SmallVector elements. This is specialized for the N=0 case
/// to avoid allocating unnecessary storage.
template <typename T, unsigned N>
struct SmallVectorStorage { // alignas(T) 强制编译器为 InlineElts 数组分配内存时,其起始地址必须满足 T 类型的对齐要求。// 确保 InlineElts 这个原始字节数组的起始地址满足类型 T 的内存对齐要求,// 从而使得后续我们可以安全地在此数组的内存上直接构造 T 类型的对象,// 避免因内存未对齐而导致的未定义行为alignas(T) char InlineElts[N * sizeof(T)];//在 C++11 中,标准库提供了 std::aligned_storage 来专门处理这种情况//std::aligned_storage_t<sizeof(T), alignof(T)> InlineElts[N];
};
拥有小型缓冲区优化
这个数组会和对象放在同一块内存,当需要存放的元素较少时就可以放在这个数组中,避免了调用malloc产生巨大的开销。