【问题标题】:How to make C++ linking take less memory如何使 C++ 链接占用更少的内存
【发布时间】:2014-09-25 10:06:21
【问题描述】:

我正在使用 大量 模板进行 C++ 大学研究项目,这些模板具有进一步的嵌套模板等。该项目是关于特定研究领域的有效索引数据结构。你可以想象:一个索引结构有很多参数需要调整,所以我们过度使用模板参数。当然,我们想用不同的参数集来测试我们的索引,所以有相当多的模板实例化。

这个项目并没有那么大。也许 50k LOC。但是,链接仍然需要 50 秒并消耗超过 7 GB 的内存 (!!!)。我在一个 32GB 的工作站上,所以对我来说一切都很好。我经常有学士和硕士生从事这个项目。问题是他们经常在具有 4 或 8 GB RAM 的笔记本电脑上工作。因此,这些学生在编译该项目时遇到了很大的麻烦。生成的测试二进制文件(即仅包含索引结构的单元测试的二进制文件)为 700 兆字节。 其中大部分是符号,因为嵌套模板会产生巨大的名称。如果我在二进制文件上使用strip,它会下降到 8 兆字节。

那么有没有办法减少链接期间的 RAM 使用量?即使使用嵌套模板,有没有办法让符号更小?

我们在 Ubuntu 14.10 下使用 g++4.9 和 std=c++11 编译。

编辑:

看起来确实是嵌套模板。我们有两个测试用例,模板嵌套非常深。用于这些测试的两个.o 文件几乎占最终二进制文件内存的 90%。它们导致方法名称超过 3000 个字符。这里没有办法不使用嵌套模板,因为它们形成了示例查询的“处理树”。使用深度嵌套的模板时,有什么方法可以使名称保持简短?

【问题讨论】:

  • 什么操作系统,编译器版本?
  • @EdChum:Ubuntu 14.10 下的 g++4.9。现在将其添加到问题中。
  • 哪个 C++ 版本? C++11 有外部模板!
  • 只是想知道你的 make 文件中是否设置了 -j 标志:gnu.org/software/make/manual/html_node/Parallel.html,听起来此时使用 clang 会更好
  • “我们想用不同的参数集测试我们的索引”——也许一些小的测试程序可能比一个大的更好?

标签: c++ templates c++11 linker


【解决方案1】:

那么有没有办法减少链接期间的 RAM 使用量?即使使用嵌套模板,有没有办法让符号更小?

您是否考虑过在客户端代码中使用 pimpl idiom

考虑你有这个包含链的情况:

A.h -> B.h -> C.h -> D.h(C 包括 D,B 包括 C 等)

假设A.h定义了AA类,B.h定义了B类等等(AA用BB实现,BB用CC实现等等)。

如果DD是大模板,用于CC的实现,模板化的代码会被编译3次,编译单元A、B、C。

现在,考虑一下如果不是 C.h 包括 D.h,而是出现以下情况会发生什么:

C.h foward 声明了一个 CCImpl *pimpl 并将它的所有方法转发给 pImpl-> 方法(并且不包括 D.h)。

C.cpp 包括 C.h 和 D.h,并实现了 CCImplCC

现在,D 将被包含一次(并为 C.cpp 编译一次)。 A 和 B 将仅包含 C.h,并带有 CImpl 前向声明。 A.h、B.h 和 C.h 不再知道模板存在。

【讨论】:

  • 我已经考虑过了,谢谢。问题是我们的大学项目是关于测量这些非常低级非常有效的数据结构的性能。因此,我们甚至无法为额外的取消引用(以及随之而来的失败的内联可能性)付出代价。我们有时甚至会计算平均周期数,每次操作只有几十个周期。
【解决方案2】:

GCC 对其使用的 RAM 有一个垃圾收集方案。

参数ggc-min-expandggc-min-heapsize 用于确定GCC 应该何时清理和释放未使用的内存(它们的默认值是系统总内存的百分比)。

你可以试试这样的:

g++ --param ggc-min-expand=0 --param ggc-min-heapsize=8192

来自 GCC 手册:

ggc-min-expand

GCC 使用垃圾收集器来管理自己的内存分配。此参数指定垃圾的最小百分比 应该允许收集器的堆在收集之间扩展。 调整这个可以提高编译速度;它对代码没有影响 一代。

默认值为 30% + 70% * (RAM/1GB),当 RAM >= 1GB 时,上限为 100%。如果 getrlimit 可用,“RAM”的概念是 实际 RAM、RLIMIT_RSS、RLIMIT_DATA 和 RLIMIT_AS 中最小的一个。如果海合会 无法在特定平台上计算 RAM,下限 使用 30%。将此参数和 ggc-min-heapsize 设置为零 导致每次有机会都发生完整的收集。这是 非常慢,但对调试很有用。

ggc-min-heapsize

垃圾收集器开始前的最小堆大小 麻烦收集垃圾。第一次收集发生在 堆扩展 ggc-min-expand% 超出 ggc-min-heapsize。再次,调 这可能会提高编译速度,并且对代码没有影响 一代。 默认值为 RAM/8,下限为 4096(4 兆字节),上限为 131072(128 兆字节)。如果 getrlimit 是 可用,“RAM”的概念是实际 RAM 中最小的, RLIMIT_RSS、RLIMIT_DATA 和 RLIMIT_AS。如果 GCC 无法计算 RAM 在特定平台上,使用下限。设置这个 参数非常大有效地禁用垃圾收集。环境 此参数和 ggc-min-expand 为零会导致完整的集合 抓住每一个机会。

更多细节:

【讨论】:

  • 虽然这可能不是主要问题,但这种方法不会减少链接时间,但很可能会增加一点
  • 你是对的,链接时间和内存消耗之间存在权衡。此外,“最佳”值可能会有所不同。
【解决方案3】:

明智地使用继承。您可能将class Foo<1,4,8,1,9,int, std::string> 作为class Bar 的基类,然后目标文件将仅提及Bar

请注意,typedef 不会引入用于链接目的的名称。

[编辑] 为了解决另一条评论中的性能问题,一个空的派生类在普通优化级别不会增加通用编译器的开销(而且通常即使在调试版本中也没有开销)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 2014-04-07
    • 2018-03-07
    • 2012-08-19
    • 2011-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多