未使用的 STL 容器是否分配内存?
Does an unused STL container allocate memory?
给定代码:
class Foo {
std::vector<int> items;
std::map<int, int> dictionary;
};
如果上面的向量或映射中没有添加任何内容,是否仍会分配一块缓冲内存? (换句话说,缓冲区分配是否总是在容器创建期间发生,还是可以推迟到调用 push_back 之类的函数?)
是否有处理初始 STL 容器缓冲区分配时间的标准,或者该行为是否允许在 STL 容器和编译器之间有所不同?
注意:这个问题不是关于这些容器会添加到 class Foo 的大小的额外字节。
(此问题的一个相关子集,重点是分配大小 Initial capacity of vector in C++。)
标准对此没有任何说明,但我专门研究的实现将为 std::vector
进行一些预分配,并且不会为 std::map
进行任何预分配。
当我有一个巨大的容器时,这实际上曾经给我很大的打击,其中的元素很小 - 不超过 10 个元素,大多数条目都有 0 大小的向量 - 向量在里面。此实现中的默认向量容量为 32,而 '32 * sizeof(vector_element) * number_of_elements' 恰好非常大。
- If nothing is ever added to the above vector or map, will either still allocate a block of memory for potential entries? (In other words, does entry allocation always happen during container creation or can it be deferred until calls to functions like push_back?)
这可能发生,是的。它实际上是容器实现的一个细节,并没有在标准中指定。
- Is there a standard for handling the timing of initial STL container allocation or is that behavior allowed to vary between STL containers and compilers?
您可以使用例如延迟创建a std::unique_ptr
用于成员,并通过调用 getter 函数创建它们。
C++ Reference 对于 C++17,默认构造函数是 noexcept
当且仅当分配器构造是 noexcept
。所以这取决于使用的分配器。在 VS 2015 中,标准构造函数是 noexcept
.
说明:这意味着如果分配器不是 noexcept
,则不会分配任何内存块。
关于你的第二个问题:同样的参考,它是 O(1)。
如前所述,这不是很好定义。但是,您可以测试一下。
例如gcc/linux。制作一个简单的程序,在 gdb 中用 -O0 -g
和 运行 编译它。那么
break main
run
break malloc
cont
现在只需在每个 malloc 上 运行 一个 backtrace
,您就会看到动态分配。使用我的 gcc 5.3.0,两个空容器都不分配堆内存,这是在第一个 push_back
/ operator[]
.
上完成的
当然,如果不是 gdb
/ malloc
.
,你应该使用你喜欢的调试器并中断你的分配器底层函数
现在,如果您考虑这两种情况。在这种情况下预分配内存有意义吗?
std::vector<int> foo;
foo.push_back(13);
好吧,从技术上讲,您可以保存 nullptr
的检查,但是使用将向量实现为 3 个指针的通常方法,不需要额外的检查。
但考虑一下
std::vector<int> foo;
foo.reserve(100);
在这种情况下,预分配会损害性能。
我找不到像 map 这样的树结构的预分配参数。
请记住,这是一个非常具体的优化。仅在有充分理由(基准!)的情况下为此进行优化。
注意:您可能想阅读小字符串优化,这是一种非常常见的技术,既相关又不同。
值得注意的是,Microsoft 的 STL 实现目前 确实 在 std::map
和 std::set
的默认构造函数中分配。 Godbolt example - 注意汇编输出第 8 行对 operator new
的调用。
是的,这绝对会影响性能。我只是在分析了一些神秘而缓慢的代码后才意识到这一点,结果发现带有很少使用的 map
成员的 class 的默认构造函数支配了 运行-时间有问题的循环。
如果您和我一样,对这个实施决定并不完全满意,我会指出 Boost.Container's counterparts do guarantee 零分配默认构造。
给定代码:
class Foo {
std::vector<int> items;
std::map<int, int> dictionary;
};
如果上面的向量或映射中没有添加任何内容,是否仍会分配一块缓冲内存? (换句话说,缓冲区分配是否总是在容器创建期间发生,还是可以推迟到调用 push_back 之类的函数?)
是否有处理初始 STL 容器缓冲区分配时间的标准,或者该行为是否允许在 STL 容器和编译器之间有所不同?
注意:这个问题不是关于这些容器会添加到 class Foo 的大小的额外字节。
(此问题的一个相关子集,重点是分配大小 Initial capacity of vector in C++。)
标准对此没有任何说明,但我专门研究的实现将为 std::vector
进行一些预分配,并且不会为 std::map
进行任何预分配。
当我有一个巨大的容器时,这实际上曾经给我很大的打击,其中的元素很小 - 不超过 10 个元素,大多数条目都有 0 大小的向量 - 向量在里面。此实现中的默认向量容量为 32,而 '32 * sizeof(vector_element) * number_of_elements' 恰好非常大。
- If nothing is ever added to the above vector or map, will either still allocate a block of memory for potential entries? (In other words, does entry allocation always happen during container creation or can it be deferred until calls to functions like push_back?)
这可能发生,是的。它实际上是容器实现的一个细节,并没有在标准中指定。
- Is there a standard for handling the timing of initial STL container allocation or is that behavior allowed to vary between STL containers and compilers?
您可以使用例如延迟创建a std::unique_ptr
用于成员,并通过调用 getter 函数创建它们。
C++ Reference 对于 C++17,默认构造函数是 noexcept
当且仅当分配器构造是 noexcept
。所以这取决于使用的分配器。在 VS 2015 中,标准构造函数是 noexcept
.
说明:这意味着如果分配器不是 noexcept
,则不会分配任何内存块。
关于你的第二个问题:同样的参考,它是 O(1)。
如前所述,这不是很好定义。但是,您可以测试一下。
例如gcc/linux。制作一个简单的程序,在 gdb 中用 -O0 -g
和 运行 编译它。那么
break main
run
break malloc
cont
现在只需在每个 malloc 上 运行 一个 backtrace
,您就会看到动态分配。使用我的 gcc 5.3.0,两个空容器都不分配堆内存,这是在第一个 push_back
/ operator[]
.
当然,如果不是 gdb
/ malloc
.
现在,如果您考虑这两种情况。在这种情况下预分配内存有意义吗?
std::vector<int> foo;
foo.push_back(13);
好吧,从技术上讲,您可以保存 nullptr
的检查,但是使用将向量实现为 3 个指针的通常方法,不需要额外的检查。
但考虑一下
std::vector<int> foo;
foo.reserve(100);
在这种情况下,预分配会损害性能。
我找不到像 map 这样的树结构的预分配参数。
请记住,这是一个非常具体的优化。仅在有充分理由(基准!)的情况下为此进行优化。
注意:您可能想阅读小字符串优化,这是一种非常常见的技术,既相关又不同。
值得注意的是,Microsoft 的 STL 实现目前 确实 在 std::map
和 std::set
的默认构造函数中分配。 Godbolt example - 注意汇编输出第 8 行对 operator new
的调用。
是的,这绝对会影响性能。我只是在分析了一些神秘而缓慢的代码后才意识到这一点,结果发现带有很少使用的 map
成员的 class 的默认构造函数支配了 运行-时间有问题的循环。
如果您和我一样,对这个实施决定并不完全满意,我会指出 Boost.Container's counterparts do guarantee 零分配默认构造。