小对象优化
c++核心原理
小对象优化采用空间换时间的策略,在容器对象内部预留一个固定大小的内部缓冲区,专门用于存储小对象数据。
以libstdc++ std::string为例, std::string通常需要动态分配内存来存储实际数据。对于大型对象,堆分配的开销相对于数据处理成本而言是可以接受的。但当频繁处理小对象时,情况就不同了:
- 分配器开销:每次调用new/delete或malloc/free都涉及复杂的内存管理算法,包括寻找合适大小的内存块、维护空闲列表等
- 内存碎片:大量小内存块的分配和释放会导致堆内存碎片化,降低内存利用率
- 缓存局部性差:堆上分配的小对象在内存中分布散乱,访问时缓存命中率低
1class string {
2 struct _Alloc_hider {
3 char* _M_p; // 指向数据的指针
4 } _M_dataplus;
5
6 size_t _M_string_length;
7
8 enum { _S_local_capacity = 15 };
9
10 union {
11 char _M_local_buf[_S_local_capacity + 1]; // 16字节栈缓冲
12 size_t _M_allocated_capacity; // 堆容量
13 };
14
15 // 指针比较法:本地缓冲地址固定
16 bool _M_is_local() const {
17 return _M_dataplus._M_p == _M_local_buf;
18 }
19
20 char* _M_data() const {
21 return _M_dataplus._M_p;
22 }
23
24 size_t capacity() const {
25 return _M_is_local() ? _S_local_capacity
26 : _M_allocated_capacity;
27 }
28};
区分当前使用内部缓冲区还是堆内存常见策略包括:
- 利用容量字段的特殊值:当某成员变量为某个特殊值时表示使用内部缓冲区
- 专用标志位:使用某个成员变量字段既存储剩余空间信息,又作为状态标识
- 指针值判断:通过检查指针是否指向内部缓冲区来判断状态(libstdc++ std::string采用)
1// 构造时设置
2string(const char* s) {
3 size_t len = strlen(s);
4 if (len <= _S_local_capacity) {
5 _M_dataplus._M_p = _M_local_buf; // 指向本地
6 memcpy(_M_local_buf, s, len + 1);
7 } else {
8 _M_dataplus._M_p = allocate(len + 1); // 堆分配
9 memcpy(_M_dataplus._M_p, s, len + 1);
10 _M_allocated_capacity = len;
11 }
12 _M_string_length = len;
13}
14
15// 拷贝构造
16string(const string& other) {
17 size_t len = other._M_string_length;
18 if (other._M_is_local()) {
19 // 短字符串: 栈拷贝
20 memcpy(_M_local_buf, other._M_local_buf, len + 1);
21 _M_dataplus._M_p = _M_local_buf;
22 } else {
23 // 长字符串: 堆分配+拷贝
24 _M_dataplus._M_p = allocate(len + 1);
25 memcpy(_M_dataplus._M_p, other._M_dataplus._M_p, len + 1);
26 _M_allocated_capacity = len;
27 }
28 _M_string_length = len;
29}
30
31// 移动构造
32string(string&& other) noexcept {
33 if (other._M_is_local()) {
34 // 短字符串: 必须拷贝(数据在other的栈上)
35 memcpy(_M_local_buf, other._M_local_buf,
36 other._M_string_length + 1);
37 _M_dataplus._M_p = _M_local_buf;
38 } else {
39 // 长字符串: 指针转移
40 _M_dataplus._M_p = other._M_dataplus._M_p;
41 _M_allocated_capacity = other._M_allocated_capacity;
42 other._M_dataplus._M_p = other._M_local_buf; // 重置为local
43 other._M_local_buf[0] = '\0';
44 }
45 _M_string_length = other._M_string_length;
46 other._M_string_length = 0;
47}
48
49~string() {
50 if (!_M_is_local()) {
51 deallocate(_M_dataplus._M_p);
52 }
53}
应用场景
- 图形/游戏引擎 - 小型数学对象(2D/3D向量、四元数、颜色值)内联存储避免cache miss
- JSON/XML解析器 - 短字符串、小数组节点内联存储,减少解析时的内存碎片