===================
== lxulxu's blog ==
===================
Hello there

小对象优化

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解析器 - 短字符串、小数组节点内联存储,减少解析时的内存碎片