【问题标题】:GCC c++11 using a lot of RAM with STL bitset<UINT_MAX>GCC c++11 使用大量带有 STL bitset<UINT_MAX> 的 RAM
【发布时间】:2013-10-04 08:06:47
【问题描述】:

今天在编译我的程序时,我注意到 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 构造函数实现的提示(显然,由 if def 为 C++11 和 C++0x 保护),正如我的一位好心同事向我展示的那样:它是使用 constexpr 声明的em>

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

谢谢

【问题讨论】:

  • 您正在静态分配一个巨大的位集,要求编译器为其运行构造函数。我知道我没有提供很多关于为什么会发生这种情况的情报,但是您是否尝试过将一些详细的输出标志传递给编译器?
  • @cyphar 500MB 不是“他妈的巨大”。对于现代机器和软件来说,这是一个合理的内存量。
  • 500MB 实在是太大了。这是一个非常大的结构。使用这种大小的结构,您可以真正存储 Linux CD 映像的位图。此外,导致它占用如此多内存的原因是优化 500MB 结构的难度。作为比较,我当前运行的 *kernel 使用了 85 MB 内存。这比 OP 程序中的一种结构小不到 5 倍。
  • @cyphar 正如丹尼尔所说,UINT 现在并不庞大,其次,我发布了这个问题,以了解 gcc 试图做出的“神奇”优化是什么,我需要社区的帮助那
  • @asalic 这不是关于特定的“神奇”优化,我认为这是关于跟踪状态作为优化的基础,甚至可能是强制 constexpr 在某些情况下是因为编译器必须知道 constexpr 的值。

标签: c++ linux gcc c++11 clang


【解决方案1】:

看看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 的唯一成员是值初始化的并且该成员是一个数组并且该数组的元素没有构造函数因此它们的值初始化是零初始化,然后数组是零初始化,然后对象是零初始化并完成。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      UINT_MAX 在大多数现代机器上约为 40 亿。因为std::bitset&lt;N&gt; 存储N 位,这意味着这样的std::bitset 需要0.5 GB 的内存(40 亿/ 8)。您看到的开销可能是由于内部编译器优化造成的。

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

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

      【讨论】:

      • 谢谢你的链接,我会用它玩一点
      猜你喜欢
      • 2023-04-01
      • 1970-01-01
      • 2012-12-07
      • 2013-07-02
      • 2016-01-13
      • 2012-03-20
      • 2013-06-25
      • 1970-01-01
      • 2016-12-25
      相关资源
      最近更新 更多