在今天编写我的程序的时候,我注意到GCC的内存消耗模式(编译步骤)中有一些很奇怪的东西(我敢肯定可以解释)。 称为“cc1plus”的进程使用大约10 GB的RAM,用于less于10000行代码的程序。 在评论和取消注释代码行后,我终于find了“罪魁祸首”:
std::bitset<UINT_MAX>
如果你想testing自己,请尝试这个很短的程序:
#include <iostream> #include <bitset> #include <climits> std::bitset<UINT_MAX> justAVar; int main() { std::cout << UINT_MAX << std::endl; return 0; }
使用-std = c ++ 11或-std = c ++ 0x编译它,请注意,即使这个小例子在编译时也会使用大量的RAM(在我的例子中gcc和gcc都是7 GB) )所以如果你不得不按下重置button,不要抱怨和诅咒神像你没有被警告过。
我的testing机器:
设置1:Debian 7.0 64,gcc 4.8.1
设置2:Ubuntu 12.04 64,gcc 4.7.3,gcc 4.8.1,铿锵3.0.6(最后一个使用-std = c ++ 0x )
关于bitset构造函数的实现的一个提示(很明显,如果是C ++ 11和C ++ 0x的话),正如我的一位同事向我展示的那样:它使用constexpr
现在的问题是:有人能解释我(我们)在这种情况下,所有这些编译器是怎么回事?
谢谢
看一下这样开始的bitset
实现:
template<size_t _Nw> struct _Base_bitset { typedef unsigned long _WordT; _WordT _M_w[_Nw]; constexpr _Base_bitset() : _M_w() { }
我们可以像这样创建一个最小的测试用例:
template<unsigned N> struct bset { unsigned int v[N/32]; constexpr bset() : v() {} }; bset<1000000000> x;
该位必须通过不断的初始化来初始化:
3.6.2初始化非局部变量[basic.start.init]
…
常量初始化被执行:
…
– 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是一个constexpr构造函数…
实际上, 在一般情况下 ,它意味着在编译时评估构造对象的内存映像,并将其分配在.data
节中。
那么,事实证明,如果内存映像只是一个很大的零,gcc足够聪明,可以计算出来并在.bss
分配对象,但是看起来 ,它首先必须创建映像并检查它。
当然,更好的方法是推断如果bitset
的唯一成员是值初始化的,并且该成员是一个数组,并且数组的元素没有构造函数,因此它们的值初始化是零初始化,那么数组是零初始化的,然后对象被零初始化,并完成。
一个std::bitset
不会动态分配它的存储,它包含在对象本身中。 这意味着编译器很可能试图跟踪其状态以允许不断的折叠和其他优化。 constexpr
在某些地方使用的事实也有助于此。 这包括一些开销来跟踪bitset
的各个部分的值将导致它分配大量的内部结构。
我不确定在哪些情况下会触发这种情况,在不同的编译器/版本上可能会有所不同,或者取决于特定的设置。
在大多数现代机器上, UINT_MAX
约为40亿。 由于std::bitset<N>
存储N
位,这就意味着这样一个std::bitset
需要0.5 GB的内存(40亿/ 8)。 您看到的开销可能是由于内部编译器优化。
http://coliru.stacked-crooked.com/上的一些小实验:
所以至少对于Clang而言,C ++ 98中缺少constexpr
将允许您在没有合理资源(不确定Coliru允许客户端有多少内存)的情况下编译该程序。