C++模板类array底层实现原理
- 内存布局
std::array
在内存中是连续存储的。它的大小是固定的,在编译期就确定了。例如std::array<int, 5>
,它会在栈上(如果是局部变量)或堆上(如果是动态分配的对象的成员)分配一块连续的内存空间,足以容纳5个int
类型的元素。
- 它的内存布局就像一个普通的C数组,元素一个接一个地排列,没有额外的堆内存分配开销(除了对象本身的存储位置)。
- 数据存储方式
std::array
使用聚合体(aggregate)来存储数据。聚合体是一种特殊的类或结构体,它的成员按照声明顺序直接存储在对象的内存中。对于std::array
,其元素就是直接存储在对象内部,这使得它与普通C数组的存储方式非常相似。
- 例如,
std::array<int, 3> arr;
,arr
对象内部直接存储3个int
类型的元素,访问arr[0]
、arr[1]
和arr[2]
就像访问普通C数组一样,通过内存偏移量来获取相应位置的元素。
性能优化(适应高并发读写场景)
- 方面入手
- 数据访问同步:在高并发读写场景下,多个线程可能同时访问
std::array
,需要考虑数据的一致性和同步问题。
- 缓存优化:由于
std::array
是连续存储的,可以利用缓存行(cache line)的特性来提高性能。不同线程访问不同元素时,如果这些元素在同一缓存行,可能会引发缓存行争用(cache line contention),需要尽量避免。
- 减少锁的粒度:如果使用锁来保证数据同步,要尽可能地减小锁的粒度,避免锁成为性能瓶颈。
- 技术手段与原理
- 互斥锁(Mutex):使用
std::mutex
来保护对std::array
的访问。原理是在访问std::array
之前,线程先获取互斥锁,访问结束后释放互斥锁。这样可以保证同一时间只有一个线程能访问std::array
,从而保证数据的一致性。例如:
#include <array>
#include <mutex>
std::array<int, 10> arr;
std::mutex arrMutex;
void writeToArr(int index, int value) {
std::lock_guard<std::mutex> lock(arrMutex);
arr[index] = value;
}
- 读写锁(Read - Write Lock):如果读操作远多于写操作,可以使用读写锁(如
std::shared_mutex
在C++17及以上)。原理是允许多个线程同时进行读操作,但只允许一个线程进行写操作,并且写操作时不允许读操作。这样可以提高读操作的并发性能。例如:
#include <array>
#include <shared_mutex>
std::array<int, 10> arr;
std::shared_mutex arrMutex;
void readFromArr(int index) {
std::shared_lock<std::shared_mutex> lock(arrMutex);
int value = arr[index];
}
void writeToArr(int index, int value) {
std::unique_lock<std::shared_mutex> lock(arrMutex);
arr[index] = value;
}
- 缓存行填充(Cache Line Padding):为了避免缓存行争用,可以对
std::array
进行缓存行填充。例如,如果缓存行大小是64字节,而int
类型大小是4字节,可以在std::array
元素之间填充一些额外的空间,使得不同线程经常访问的元素不在同一缓存行。原理是减少缓存行的频繁更新,提高缓存命中率。例如:
#include <array>
#include <iostream>
class CacheLinePadding {
public:
alignas(64) std::array<int, 10> arr;
};
- 无锁数据结构(Lock - Free Data Structures):对于一些简单的场景,可以使用无锁数据结构。例如,使用
std::atomic
类型来实现无锁的计数器等简单数据结构,原理是利用硬件提供的原子操作指令,避免使用锁带来的开销,提高并发性能。但实现复杂的无锁std::array
替代方案较为困难,因为需要处理好数据一致性和内存顺序等问题。