【问题标题】:Compiling larger (~6MB) map initializing C++ file with gcc使用 gcc 编译较大的 (~6MB) 映射初始化 C++ 文件
【发布时间】:2013-09-07 19:07:25
【问题描述】:

我正在尝试编译一个 5.7 MB 大的 C++ 文件。我正在 64 位 Linux 系统上构建一个 64 位 Linux 可执行文件。不幸的是,g++ 4.7.2 不合作:

g++: internal compiler error: Killed (program cc1plus)

使用top 观察表明该进程在此之前达到了大约 2.2 gigs 的内存。我尝试设置--param gcc-min-expand=0 并且还玩了--param gcc-min-heapsize,但这并没有解决问题。使用 -O0 禁用优化也没有帮助。

我也试过用clang编译,但结果差不多。它在超过 2 gigs 的内存后也出现了段错误。我没有尝试使用 clang 的任何额外选项,因为我对它不太熟悉。

有问题的源文件包含几个地图的 C++11 风格初始化。

typedef std::map<std::string, int> StringToIntMap;
StringToIntMap someData = {{"SOMESTRING", 1}, ..};

我想要的是最好用gcc编译文件,虽然如果clang可以代替,我也可以忍受它。从了解内部情况的人那里了解幕后发生的事情也很有帮助。如果我有一个包含 300 000 个元素的映射,其中字符串的长度约为 5 个字节,并且每个元素对应一个 int,那么这就是几兆字节的数据,我无法轻易想象初始化程序如何将其炸毁到需要千兆字节才能编译。

为了抢占我不应该有这么大的源文件的 cmets。我知道我可以在运行时从数据文件中读取数据,这就是程序现在所做的,但我的用例是程序的执行时间是最重要的因素。

【问题讨论】:

  • 哈哈。我确信标准的附录 B 允许编译器比这更早开始失败:/
  • 将其移入源代码实际上并不会带来太多好处。现在,加载可执行文件需要 M 时间 + N 时间来初始化地图。预加载数据后,您最终会得到一个需要(大约)M+N 时间来加载的可执行文件。给定需求分页,这将发生在 M 时间开始执行和 N 时间在运行时分页数据。终极区别:更多的工作,但执行速度几乎没有差异。
  • @JerryCoffin 现在如果这是boost::flatmap,它可能会好很多,但即便如此,constexpr 初始化可能会更好。
  • 如果你创建一个对向量,然后动态生成地图会发生什么?
  • @Mehrdad 你浪费内存 :) 你可以只使用带有 lower_boundupper_boundequal_range 的向量 :/ (或者实际上,提升平面地图,它使用更友好的界面来做到这一点)

标签: c++ gcc g++ clang


【解决方案1】:

允许编译器对许多语言结构中支持的级别/数量设置实现定义的限制。

附录 B 列出了符合标准的编译器所需的最少数量

来自附录 B,将最相关的部分加粗:

这些限制可能会限制数量,包括以下描述的数量或 其他。建议将每个数量后面的括号内的数字作为 该数量的最小值。然而,这些数量只是指导方针 不确定合规性。

  • 复合语句、迭代控制结构和选择控制结构的嵌套级别 [256]。
  • 条件包含的嵌套级别 [256]。
  • 指针、数组和函数声明符(任意组合)修改类、算术或 incom- 声明中的完整类型 [256]。
  • 在完整表达式中嵌套括号表达式的级别 [256]。
  • 内部标识符或宏名称中的字符数 [1 024]。
  • 外部标识符中的字符数 [1 024]。
  • 一个翻译单元中的外部标识符 [65 536]。
  • 在一个块 [1 024] 中声明的具有块范围的标识符。
  • 在一个翻译单元 [65 536] 中同时定义宏标识符。
  • 一个函数定义中的参数 [256]。
  • 一个函数调用中的参数 [256]。
  • 一个宏定义中的参数 [256]。
  • 一个宏调用中的参数 [256]。
  • 一个逻辑源代码行中的字符 [65 536]。
  • 字符串文字中的字符(连接后)[65 536]。
  • 对象的大小 [262 144]。
  • #include 文件的嵌套级别 [256]。
  • switch 语句的 case 标签(不包括任何嵌套 switch 语句的标签)[16 384]。
  • 单个类中的数据成员 [16 384]。
  • 单个枚举中的枚举常量 [4 096]。
  • 单个成员规范中嵌套类定义的级别 [256]
  • atexit() [32] 注册的函数。
  • at_quick_exit() [32] 注册的函数。
  • 直接和间接基类 [16 384]。
  • 单个类的直接基类 [1 024]。
  • 在单个类中声明的成员 [4 096]。
  • 类中的最终覆盖虚函数,可访问与否 [16 384]。
  • 类 [1 024] 的直接和间接虚基。
  • 类 [1 024] 的静态成员。
  • 类 [4 096] 中的朋友声明。
  • 类 [4 096] 中的访问控制声明。
  • 构造函数定义中的成员初始化器 [6 144]。
  • 一个标识符的范围限定 [256]。
  • 嵌套的外部规范 [1 024]。
  • 递归 constexpr 函数调用 [512]。
  • 模板声明中的模板参数 [1 024]。
  • 递归嵌套模板实例化,包括模板参数推导期间的替换 (14.8.2) [1 024]。
  • 每个尝试块的处理程序 [256]。
  • 在单个函数声明中抛出规范 [256]。
  • 占位符数 (20.8.9.1.4) [10]

现在,初始化列表实际上只是由许多参数“构造”而成,显然 GCC 并不完全支持您提供的数量/体积。

手册页中可能有一些选项可以缓解这种情况:

  • -mlarge-data(默认)
  • -mlarge-text(也是默认值)

【讨论】:

  • 这当然解释了为什么编译器根据标准可能会失败,但并不能真正解释超过 2 gigs 的内存使用。初始化器由许多参数构建而成,在这种情况下,我猜它们的形式为pair &lt;std::string, int&gt; (key, value)。尝试构建一个过长的此类参数列表如何使用 2 GB 的内存?
  • @user1264727 我不知道。有一种方法可以找到:调试编译器(它是开源的)。或者,您知道,您可以更改代码以使用不同的方法。
  • 这是一个更多出于对内部工作原理的好奇而产生的问题 :) 现在似乎很清楚,即使可能强制编译,使用这种方法也不值得。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-16
  • 2015-10-04
  • 2021-06-03
  • 2021-12-31
  • 1970-01-01
  • 2018-05-01
  • 1970-01-01
相关资源
最近更新 更多