【问题标题】:Long compilation time for program with static allocation静态分配程序编译时间长
【发布时间】:2011-06-26 02:33:14
【问题描述】:

如果有人能告诉我为什么要编译这个程序,我将不胜感激:

double data[123456789];  
int main() {}

比编译这个要长 10 倍:

int main() {
    double* data=new double[123456789];
}

当两者都编译时:

$ g++ -O0

并且可执行文件的大小几乎相同。

我在 Ubuntu 10.04 上使用 gcc 4.4.3。

谢谢。

【问题讨论】:

  • 非常有趣的观察。我注意到 clang++(来自 LLVM 的 2.8 版本)具有完全相同的行为。我怀疑时间花在了链接器甚至汇编器上,而不是编译器上……
  • 快速测试确认链接器所花费的时间(g++ -c -O0 对于两个来源几乎都是即时的)

标签: gcc


【解决方案1】:

动态分配

您的第二个程序在运行时分配内存;从编译器的角度来看,编译以下任何内容都没有真正的区别:

double *data = new double[123456789];
double *data = malloc(123456789);
double data  = sqrt(123456789);

它们都做不同的事情,但编译器需要做的就是生成对具有固定参数的外部函数的调用。如果您使用g++ -S 生成程序集,您可以看到这一点:

.text
main:
    subq    $8, %rsp          /* Allocate stack space. */
    movl    $987654312, %edi  /* Load value "123456789 * 8" as argument. */
    call    _Znam             /* Call the allocation function. */
    xorl    %eax, %eax        /* Return 0. */
    addq    $8, %rsp          /* Deallocate stack space. */
    ret

这对于任何编译器都可以轻松生成,任何链接器都可以链接。

静态分配

不过,正如您所注意到的,您的第一个程序有点小技巧。如果我们查看它的程序集,我们会发现发生了一些不同的事情:

.text
main:
    xorl    %eax, %eax        /* Return 0. */
    ret

.bss
data:
    .zero   987654312         /* Reserve "123456789 * 8" bytes of space. */

程序首次启动时,生成的程序集要求保留123456789 * sizeof(double) 字节的空间。当它被组装并随后被链接时(在幕后发生的是你只是运行g++ foo.c),链接器ld实际上将在内存中分配所有这些保留空间。这就是时间的流逝。如果您在运行g++ 的同时运行top,您将看到ld 占用了大量系统内存。

减小的可执行文件大小

一个合理的问题可能是“如果在链接时保留内存,为什么我的可执行文件最终不会很大?”。答案隐藏在程序集中的.bss 标记中。这告诉链接器,它下面定义的数据不应该存储在最终的可执行文件中,而是在运行时分配为零。

这给我们留下了以下一系列步骤:

  1. 汇编器告诉链接器它需要创建一段 1GB 长的内存。

  2. 链接器继续分配此内存,准备将其放入最终的可执行文件中。

  3. 链接器意识到此内存位于.bss 部分并标记为NOBITS,这意味着数据只是0,不需要物理放置到最终的可执行文件中。它避免写出 1GB 的数据,而只是丢弃分配的内存。

  4. 链接器仅将编译后的代码写入最终的 ELF 文件,生成一个小的可执行文件。

更智能的链接器可能能够避免上面的步骤 2 和 3,从而使您的编译时间更快。实际上,像您这样的场景在实践中出现的频率并不高,因此值得进行这样的优化。

动态与静态分配

如果您正在尝试确定在您的程序中实际使用上述哪个(动态分配与静态分配),这里有一些想法:

  • 链接器需要使用与最终程序一样多的内存(加上一点)。如果要静态分配 4GB 的 RAM,则链接器需要 4GB 的 RAM。这并不隐含在链接器的工作方式中,而似乎只是它们的实现方式。

  • 在分配大量内存时,动态分配可以让您更好地处理错误(在屏幕上显示一条用户友好的消息,说明您没有足够的内存,而不仅仅是无法加载可执行文件来自操作系统的消息发送给用户);

  • 动态分配内存允许您根据实际需要选择分配多少内存。 (如果data是缓存,用户可以选择缓存大小,如果是存储中间结果,可以根据问题大小等)

  • 动态分配内存允许您稍后释放它,如果您的程序在使用完内存后需要继续执行更多工作。

最后,如果以上几点无关紧要,并且您可以处理更长的编译时间,那可能就没有太大关系了。静态分配内存可以简单得多,并且通常是较小内存量或一次性应用程序的正确方法。

【讨论】:

  • “链接器继续并分配此内存”——这是 LD 中的一个错误,现已修复:GNU ld (GNU Binutils for Ubuntu) 2.22 显示问题,而2.24.51.20131021 没有。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-21
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 2017-08-14
  • 1970-01-01
相关资源
最近更新 更多