Skip to content

Latest commit

 

History

History
437 lines (344 loc) · 9.68 KB

2020_08_12_msvc_stl_containers_implementation.md

File metadata and controls

437 lines (344 loc) · 9.68 KB

MSVC C++ STL Containers Implementation

为了精确统计 stl containers 的内存占用,把 msvc 中常用的容器都研究了一下。

  • 代码参考 VS2017,在 VS2012 & VS2017 中测试通过
  • 文章中的"内存消耗",都是容器动态分配的内存

常用容器

_Container_proxy

  • debug build 每个容器还会额外申请一个 _Container_proxy
  • 注意:sizeof(_Container_proxy) 32/64-bit 大小不一样
struct _Container_proxy
{
    const _Container_base12* _Mycont;
    _Iterator_base12* _Myfirstiter;
};

std::vector

  • container, _Container_alloc, _Container_val
  • 所有容器,基本都是这个层次关系
  • 数据结构如下
class _Vector_val
{
    pointer _Myfirst; // pointer to beginning of array
    pointer _Mylast;  // pointer to current end of sequence
    pointer _Myend;   // pointer to end of array
};

class _Vector_alloc
{
    // _Alty = type allocator, sizeof(_Alty) == 0
    // 实际数据结构由 _Vector_val 决定
    _Compressed_pair<_Alty, _Vector_val<_Val_types>> _Mypair;
};

class vector : public _Vector_alloc
{
};
  • vector 很简单,array of T,动态扩容
  • 扩容策略:newCapacity = oldCapacity + oldCapacity/2
  • 注意:不做 shrink_to_fit(),永远不释放内存
  • _Myfirst = T0
  • _Mylast = _Myfirst + vector::size()
  • _Myend = _Myfirst + vector::capacity()
+----+----+----+----+-----+----+
| T0 | T1 | T2 | T3 | ... | Tn |
+----+----+----+----+-----+----+

内存消耗

sizeof(T) * vector.capacity()

std::list

  • 链表,数据结构如下
class _List_node
{
    _Nodeptr _Next;
    _Nodeptr _Prev;
    _Value_type _Myval;
};

class _List_val
{
    _Nodeptr _Myhead;  // pointer to head node
    size_type _Mysize; // number of elements
};

class _List_alloc
{
    // _Alnode = node allocator
    _Compressed_pair<_Alnode, _List_val<_Val_types>> _Mypair;
};

class _List_buy : public _List_alloc
{
    // just add some functions
};

class list : public _List_buy
{    
};
  • 标准的双向链表实现
  • 利用 _Myhead 多一个 dummy node 的额外开销,来实现 list::end()
  • list._Myhead->_Next = first element
  • list._Myhead->_Prev = last element
       list._Myhead
             \                                        last one
              V                                         /
             +------+     +------+     +------+     +------+
last one <-- | prev | <-- | prev | <-- | prev | <-- | prev |
             +------+     +------+     +------+     +------+
             | next | --> | next | --> | next | --> | next | --> list._Myhead
             +------+     +------+     +------+     +------+
             |  val |     |  val |     |  val |     |  val |
             +------+     +------+     +------+     +------+

内存消耗

sizeof(_List_node) * (list.size() + 1)

std::deque

#define _DEQUEMAPSIZ    8    /* minimum map size, at least 1 */
#define _DEQUESIZ    (sizeof (value_type) <= 1 ? 16 \
    : sizeof (value_type) <= 2 ? 8 \
    : sizeof (value_type) <= 4 ? 4 \
    : sizeof (value_type) <= 8 ? 2 \
    : 1)    /* elements per block (a power of 2) */

class _Deque_val
{
    T**       _Map;     // pointer to array of pointers to blocks
    size_type _Mapsize; // size of map array, zero or 2^N
    size_type _Myoff;   // offset of initial element
    size_type _Mysize;  // current length of sequence
};

class _Deque_alloc
{
    _Compressed_pair<_Alty, _Deque_val<_Val_types>> _Mypair;
};

class deque : public _Deque_alloc
{
};

// extra bytes
struct _Container_proxy
{
    const _Container_base12* _Mycont;
    _Iterator_based12* _Myfirstiter;
};
  • _Map 一个 block 的数组
  • 一个 block 保存 1 ~ 16 个元素 (_DEQUESIZ)
  • 根据 _DEQUESIZ 的设计,deque 不适合保存 sizeof 过大的数据
  • deque 还额外有一个 _Container_proxy。其他容器 debug build 也会有 _Container_proxy
  • 但 deque 的 release build 也会有(怀疑是bug)
  • 对于需要大量 push_back(), push_front() 的行为,deque 比较快
  • 下面是一个模拟的数据
  • sizeof(T) == 4
  • _Mapsize = 8
  • _Myoff = 2
  • _Mysize = 5
_Map
  +---+---+---+---+---+---+---+---+
  | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
  +---+---+---+---+---+---+---+---+
   /      \
  V        V
+----+     +----+ 
| .. |     | T2 |
+----+     +----+
| .. |     | T3 |
+----+     +----+
| T0 |     | T4 |
+----+     +----+
| T1 |     | .. |
+----+     +----+

内存消耗

sizeof(void*) * _Mapsize
  + num_of_not_null_map_slots * (sizeof(T) * _DEQUESIZ)
  + sizeof(_Container_proxy)

std::map<K, V>

struct _Tree_node
{
    _Nodeptr _Left;     // left subtree, or smallest element if head
    _Nodeptr _Parent;   // parent, or root of tree if head
    _Nodeptr _Right;    // right subtree, or largest element if head
    char _Color;        // _Red or _Black, _Black if head
    char _Isnil;        // true only if head (also nil) node
    _Value_type _Myval; // the stored value, unused if head
};

class _Tree_val
{
    _Nodeptr _Myhead;  // pointer to head node
    size_type _Mysize; // number of elements
};

class _Tree_comp_alloc
{
    _Compressed_pair<key_compare,
        _Compressed_pair<_Alnode, _Tree_val<_Val_types>>> _Mypair;
};

class _Tree : public _Tree_comp_alloc
{
    // ordered red-black tree for map/multimap/set/multiset
};

class map : public _Tree
{
};
  • 标准的 red-black tree 实现
  • 多一个 dummy head node
  • _Tree_node._Myval = std::pair<K,V>

内存消耗

sizeof(_Tree_node) * (map.size() + 1)

std::set

  • 结构和 map 类似
  • _Tree_node._Myval = K

内存消耗

sizeof(_Tree_node) * (set.size() + 1)

std::unordered_map<K, V>

class _Hash
{
    // hash table -- list with vector of iterators for quick access

    _Traits _Traitsobj; // traits to customize behavior
    _Mylist _List;      // list of elements, must initialize before _Vec
    _Myvec _Vec;        // vector of list iterators, begin() then end()-1
    size_type _Mask;    // the key mask
    size_type _Maxidx;  // current maximum key value
};

class unordered_map : public _Hash
{
};
  • 标准 HashTable 实现
  • 所有元素放在 _List 中
  • _Vec 作为 HashTable 的 buckets,保存着 _List 的 iterator
  • _Vec[i] 和 _Vec[i+1] 为一组,对应 _List 中的一段数据
  • Hash(key) 找到 i,然后遍历 _List 中 _Vec[i] ~ _Vec[i+1] 所有的元素即可
  • 留意:_List & _Vec,有两个 _Container_proxy
        one bucket
_Vec     /
    /-------\
    +---+---+---+---+
    | 0 | 1 | 2 | 3 |
    +---+---+---+---+
      |   \
      |    --------------
_List V                  V
    +----+    +----+    +----+    +----+
    | V1 | -> | V2 | -> | V3 | -> | V4 | ...
    |    | <- |    | <- |    | <- |    |
    +----+    +----+    +----+    +----+

内存消耗

// _List_node = { void* prev; void* next; std::pair<K,V> elem; }
sizeof(_List::iterator) * (map.bucket_count()*2)
  + sizeof(_List_node) * (map.size() + 1)

std::unordered_set

  • 结构和 unordered_map 类似

内存消耗

// _List_node = { void* prev; void* next; T elem; }
sizeof(_List::iterator) * (map.bucket_count()*2)
  + sizeof(_List_node) * (map.size() + 1)

std::basic_string

  • std::string = basic_string<char, char_trais, allocator>
_BUF_SIZE = 16 / sizeof (value_type) < 1 ? 1 : 16 / sizeof (value_type)};

class _String_val
{
    union _Bxty {
        value_type _Buf[_BUF_SIZE];
        pointer _Ptr;
    } _Bx;
    size_type _Mysize; // current length of string
    size_type _Myres;  // current storage reserved for string
};

class _String_alloc
{
    _Compressed_pair<_Alty, _String_val<_Val_types>> _Mypair;
};

class basic_string : public _String_alloc
{
};
  • _BUF_SIZE = [1, 16],由 sizeof(T) 大小决定
  • 利用 Bx,对于短字符串,不进行内存申请
  • capacity() 返回的值,减去了 '\0' 的空间
  • 注意:不做 shrink_to_fit(),永远不释放内存

内存消耗

sizeof(T) * (s.capacity()+1) > 16 ? sizeof(T) * (s.capacity()+1) : 0;

std::shared_ptr

template <class _Ty>
class _Ref_count : public _Ref_count_base
{
    _Ty* _Ptr;
};

class _Ptr_base
{
    T* _Ptr;
    _Ref_count_base* _Rep;
};

class shared_ptr : public _Ptr_base {};
  • class _Ref_count_del, class _Ref_count_del_alloc 也继承 _Ref_count_base
  • 不过内存占用都是 "_Ty* _Ptr",直接统计 sizeof(std::_Ref_count) 即可

内存消耗

sizeof(std::_Ref_count<T>)

std::function

#define _CLASS_FUNC_CLASS_END_0X( \
    ...\
	typedef void (*_Pfnty)(); \
	union \
		{	/* storage for small wrappers */ \
		_Pfnty _Pfn[3]; \
		void *_Pobj[3]; \
		long double _Ldbl;	/* for maximum alignment */ \
		char _Alias[3 * sizeof (void *)];	/* to permit aliasing */ \
		} _Space; \
	_Ptrt *_Impl; \
	};
  • 没有动态内存分配,函数指针保存于 _Space