尝试减小Go程序的可执行文件大小

时间:2019-07-19 19:19:13

标签: go binaryfiles

编辑/澄清:

看来我在这里无法解释自己。我不是在批评Go,不是运行时,也不是可执行文件很大的事实。我也不是想说Go不好而C很好。

我只是指出,编译后的可执行文件似乎总是至少约1MB(大概是运行时的开销),并且导入软件包似乎会将整个软件包放入其中,而不管其用途如何。

我的实际问题是,这两个点是默认行为还是唯一行为?我给出了一些C语言程序的示例,这些示例在代码方面等效于Go程序,但是我为它们精心挑选了编译器和链接器标志,以避免与任何外部C运行时进行链接(我仅通过Dependency Walker进行了验证,确定)。 C示例的目的是演示实际代码有多小,并演示您确实需要某些东西而仅导入所需内容的情况。

我实际上认为这种行为(以防万一)是默认情况下的一个好设置,但是我认为可能会有一些编译器或链接器标志来更改此设置。现在,我不认为您不希望运行时或运行时的一部分是明智的。但是,我认为有选择地包括包装的各个部分并不是一件奇怪的事情。让我解释一下:

比方说,我们正在用C / C ++编写程序,其中包含一个带有大量函数的巨大头文件,但我们仅使用其中的一小部分。在这种情况下,最终可能会得到一个不包含该头文件中未使用的代码的可执行文件。 (实际示例:一个支持2D / 3D / 4D向量和矩阵,四元数等的数学库。所有这些库都有2个版本,一个用于32位浮点数,一个用于64位浮点数+从一个到另一个的转换)< / p>

这是我一直在寻找的东西。我完全理解这样做可能会在某些情况下引起问题,但仍然如此。并不是说Go没有其他可能导致严重问题的东西。.他们拥有“不安全”软件包,如果需要的话,可以使用该软件包,但是就像“自担风险”之类的软件包。


原始问题:

使用Go(golang)一段时间后,我决定研究它生成的可执行文件。我看到我的项目仅针对可执行文件的时钟就超过4.5MB,而另一个在复杂度和范围上相似但使用C / C ++(与MSVC编译)编写的项目却不到100KB。

所以我决定尝试一些尝试。我已经用C和Go编写了非常愚蠢和无用的简单程序来比较输出。 对于C,我正在使用MSVC,以发布模式编译64位可执行文件,并且未与C运行时链接(据我所知,Go可执行文件仅在使用CGO时才与之链接)

第一次运行:一个简单的无穷循环,就是这样。没有打印,没有与操作系统的交互,什么都没有。

C:

#include "windows.h"

int main() 
{
    while (true);
}

void mainCRTStartup()
{
    main();
}

GO:

package main

func main() {
    for {
    }
}

结果:

C:3KB可执行文件。一无所有

GO:1,057 KB可执行文件。取决于KERNEL32.DLL

的29个过程

那里存在巨大差异,但是我认为这可能是不公平的。因此,下一个测试我决定删除循环,只是编写一个程序,该程序立即以退出代码13返回:

C:

    #include "windows.h"

    int main() 
    {
        return 13;
    }

    void mainCRTStartup()
    {
        ExitProcess(main());
    }

GO:

package main

import "os"

func main() {
    os.Exit(13)
}



结果:

C:4KB可执行文件。取决于KERNEL32.DLL的1个过程

GO:1,281 KB可执行文件。取决于KERNEL32.DLL中的31个过程

Go可执行文件似乎“ blo肿”。我了解到,与C不同,Go将大量运行时代码放入可执行文件中,这是可以理解的,但不足以解释其大小。

另外,Go似乎可以在软件包粒度上运行。我的意思是,它不会塞入您不使用的可执行程序包中,但是,即使您只需要一个很小的子集,即使您根本不使用它,也可以导入所有包。 。例如,仅导入“ fmt”而未在其中调用任何内容,会将先前的可执行文件从1,281KB扩展到1,777KB。

我是否错过了一些类似于Go编译器的标志来告知它不那么(肿(我知道我可以设置很多标志,还为本机编译器和链接器提供标志,但我对此没有找到特别是),或者这仅仅是2019年没人关心的事情了,因为实际上只有几兆字节?

1 个答案:

答案 0 :(得分:0)

以下是Go程序所包含的一些内容,而C程序所没有的内容:

  • 容器类型,例如哈希映射和数组及其关联函数

  • 内存分配器,针对多线程程序进行了优化

  • 并发垃圾收集器

  • 线程类型和函数,例如互斥锁,条件变量,通道和线程

  • 调试工具,例如堆栈跟踪转储和SIGQUIT处理程序

  • 反射代码

(如果您确切想知道其中包含的内容,可以使用调试工具查看二进制文件中的符号。在macOS和Linux上,可以使用nm将符号转储到程序中。)

问题是-大多数Go程序都使用所有这些功能!很难想象不使用垃圾回收器的Go程序。因此,Go的创建者尚未创建从程序中删除此代码的特殊方法-因为没人需要此功能。毕竟,您真的关心"Hello, world!"有多大吗? 不,您没有。

通过常见问题解答Why is my trivial program such a large binary?

  

gc工具链中的链接器默认情况下会创建静态链接的二进制文件。因此,所有Go二进制文件都包含Go运行时,以及支持动态类型检查,反射甚至紧急时间堆栈跟踪所必需的运行时类型信息。

还请记住,如果要在Windows上使用MSVC进行编译,则可能正在使用DLL运行时,例如MSVCR120.DLL ...,大约1 MB。