【问题标题】:gcc/g++: error when compiling large filegcc/g++:编译大文件时出错
【发布时间】:2010-12-21 11:48:27
【问题描述】:

我有一个自动生成的 C++ 源文件,大小约为 40 MB。它主要由一些向量的 push_back 命令和应该推送的字符串常量组成。

当我尝试编译此文件时,g++ 退出并说它无法保留足够的虚拟内存(大约 3 GB)。谷歌搜索这个问题,我发现使用命令行开关

--param ggc-min-expand=0 --param ggc-min-heapsize=4096

可以解决问题。但是,它们似乎仅在启用优化时才起作用。

1) 这真的是我正在寻找的解决方案吗?

2) 或者是否有更快、更好(编译需要很长时间并激活这些选项)的方法来做到这一点?

祝你好运,

亚历山大

更新:感谢所有好主意。我尝试了其中的大多数。使用数组而不是几个 push_back() 操作减少了内存使用量,但由于我试图编译的文件太大,它仍然崩溃,只是后来才崩溃。在某种程度上,这种行为真的很有趣,因为在这样的设置中没有太多需要优化的地方——GCC 在幕后做了什么来消耗这么多内存? (我在编译时也禁用了所有优化并得到了相同的结果)

我现在切换到的解决方案是从我使用objcopy 从原始文件创建的二进制对象文件中读取原始数据。这是我最初不想做的,因为用高级语言(在本例中是 Perl)创建数据结构比用 C++ 更方便。

但是,在 Win32 下运行它比预期的要复杂。 objcopy 好像生成的是ELF格式的文件,我手动设置输出格式为pe-i386的时候出现的一些问题好像消失了。目标文件中的符号按标准以文件名命名,例如转换文件inbuilt_training_data.bin 将产生这两个符号:binary_inbuilt_training_data_bin_start 和 binary_inbuilt_training_data_bin_end。我在网上找到了一些教程,声称这些符号应该声明为extern char _binary_inbuilt_training_data_bin_start;,但这似乎不对——只有extern char binary_inbuilt_training_data_bin_start; 对我有用。

【问题讨论】:

  • 这属于 thedailywtf.com。
  • 为了更好地帮助您,请发布您拥有自动生成的 C++ 源文件的原因以及您正在编译的目标(即:可执行文件、库等)。
  • 我们应该让他发布代码吗,兄弟们? :-)
  • 我知道 Markdown JavaScript 在我输入几 KB 后开始占用我的 CPU;我想 40 MB 会给我一个旋转的死亡彩虹。

标签: gcc g++ compilation large-files


【解决方案1】:

您最好改用常量数据表。例如,不要这样做:

void f() {
    a.push_back("one");
    a.push_back("two");
    a.push_back("three");
    // ...
}

尝试这样做:

const char *data[] = {
    "one",
    "two",
    "three",
    // ...
};

void f() {
    for (size_t i = 0; i < sizeof(data)/sizeof(data[0]); i++) {
        a.push_back(data[i]);
    }
}

编译器可能会更有效地生成大型常量数据表,而不是包含许多 push_back() 调用的大型函数。

【讨论】:

    【解决方案2】:

    你能在不生成 40 MB 的 C++ 的情况下做同样的问题吗?这不仅仅是我使用过的一些操作系统。也许是一个循环和一些数据文件?

    【讨论】:

    • 一个循环和数据文件...你能扩展你的答案吗?也许举个简单的例子。一句话提示,他可能什么也达不到……
    【解决方案3】:

    听起来您的自动生成的应用看起来像这样:

    push_back(data00001);
    ...
    push_back(data99999);
    

    为什么不把数据放到一个外部文件中,让程序循环读取这些数据呢?

    【讨论】:

      【解决方案4】:

      如果您只是连续产生大量对push_back() 的调用,您可以将其重构为如下形式:

      // Old code:
      v.push_back("foo");
      v.push_back("bar");
      v.push_back("baz");
      
      // Change that to this:
      {
          static const char *stuff[] = {"foo", "bar", "baz"};
          v.insert(v.end(), stuff, stuff + ARRAYCOUNT(stuff));
      }
      

      其中ARRAYCOUNT是一个宏定义如下:

      #define ARRAYCOUNT(a) (sizeof(a) / sizeof(a[0]))
      

      如果你有很多这样的块,额外的大括号只是为了避免名称冲突;或者,您可以为 stuff 占位符生成一个新的唯一名称。

      如果这仍然不起作用,我建议将您的源文件分成许多较小的源文件。如果您有许多单独的功能,这很容易;如果你有一个巨大的功能,你将不得不更加努力,但它仍然非常可行。

      【讨论】:

        【解决方案5】:

        为了补充这里的一些答案,您最好生成一个二进制对象文件并直接链接它——而不是编译由const char[] 组成的文件。

        我最近在使用 gcc 时遇到了类似的问题。 (大约 60 MB 的 PNG 数据分成大约 100 个头文件。)将它们全部包含在内是最糟糕的选择:所需的内存量似乎随着编译单元的大小呈指数增长。

        【讨论】:

        • 您应该将 PNG 数据保存在源文件中,而不是标题中。头文件应该只有extern const char img_data[]; extern const size_t img_data_size;,源文件应该有char img_data[] = {...}; const size_t img_data_size = sizeof(img_data);编译器处理起来容易得多,使用图像数据的文件在图像发生变化时不需要重新编译。
        • @Adam Rosenfeld:是的,这是可行的,但它可能无法解决实际问题,即首先通过编译器的二进制流。 (二进制数据 -> C 源代码 -> 编译器 -> 二进制数据 - 听起来不太对,是吗?)顺便说一句,“链接器”解决方案最终看起来与您的完全一样:使用仅包含 extern char* 的标头+ 外部尺寸。
        • ...我想我在 MacOS X 上编译时这样做了,它的链接器不同,编译器套件没有明显的方法将二进制数据转换为目标文件。但只要你有一个包含数据开始 + 数据大小(或数据开始 + 数据结束,可能是)这两个符号的目标文件,谁创建它以及如何创建它并不重要,不是吗?
        【解决方案6】:

        如果您无法重构代码,您可以尝试增加您拥有的交换空间量,前提是您的操作系统支持大地址空间。这应该适用于 64 位计算机,但 3 GB 可能对于 32 位系统来说太大了。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-28
          • 1970-01-01
          • 2015-06-27
          相关资源
          最近更新 更多