2

今天编译我的程序时,我注意到 GCC 的内存消耗模式(编译步骤)中有一些非常奇怪的东西(我确信可以以某种方式解释)。名为“cc1plus”的进程使用大约 10 GB 的 RAM 来运行一个少于 10 000 行代码的程序。在注释和取消注释代码行之后,我终于找到了“罪魁祸首”:

std::bitset<UINT_MAX>

如果您想测试自己,请尝试这个非常简短的程序:

#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 的两个盒子都是 7 GB,clang 是 2.6 ) 因此,如果您必须按下重置按钮,请不要像没有被警告过一样抱怨和诅咒众神。

我的测试机器:

设置 1:Debian 7.0 64,gcc 4.8.1

设置2:Ubuntu 12.04 64,gcc 4.7.3,gcc 4.8.1,clang 3.0.6(最后一个使用-std=c++0x

正如我的一位好心同事向我展示的那样,关于 bitset 构造函数的实现的一个提示(显然,由 C++11 和 C++0x 的 if def 保护):它是使用constexpr声明的

现在的问题是:有人可以向我(我们)解释一下在这种情况下所有这些编译器发生了什么吗?

谢谢

4

3 回答 3

2

查看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;

bitset 必须通过常量初始化来初始化:

3.6.2 非局部变量的初始化【basic.start.init】

...

执行常量初始化:

...

— 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是 constexpr 构造函数...

实际上,在一般情况下,这意味着在编译时评估构造对象的内存映像并在.data部分中分配它。

好吧,事实证明,如果内存映像只是很多零,gcc 足够聪明,可以找出并分配对象.bss,但看起来,它首先必须创建映像并检查它。

当然,更好的方法是推断如果 , 的唯一成员bitset是值初始化的,并且该成员是一个数组,并且数组的元素没有构造函数,因此它们的值初始化为零初始化,那么数组是零初始化的,然后对象是零初始化的并完成。

于 2013-10-04T09:29:55.380 回答
0

Astd::bitset不会动态分配其存储空间,它包含在对象本身中。这意味着编译器很可能会尝试跟踪其状态以允许常量折叠和其他优化。在某些地方使用的事实constexpr也促成了这一点。这包括一些开销来跟踪单个部分的值,这bitset将导致它分配大量的内部结构。

我不确定在哪种情况下会触发它,并且在不同的编译器/版本上可能会有所不同,或者取决于某些设置。

于 2013-10-04T08:24:43.273 回答
0

UINT_MAX在大多数现代机器上约为 40 亿。因为 astd::bitset<N>存储N位,所以这意味着这样的std::bitset. 您看到的开销可能是由于内部编译器优化造成的。

http://coliru.stacked-crooked.com/上的一些小实验:

因此,至少对于 Clang 而言,constexprC++98 中的缺失将允许您在合理的资源上毫无问题地编译该程序(不确定 Coliru 允许客户端使用多少内存)。

于 2013-10-04T08:28:21.017 回答