dragon87836215
2019-07-19 19:19 阅读 81
已采纳

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

This question already has an answer here:

EDIT / CLARIFICATION:

It seems that I have failed in explaining myself here. I am not criticizing Go, or it's runtime, or the fact that the executables are large. I was also not trying to say that Go is bad while C is good.

I was merely pointing out that the compiled executable seems to always be at least around 1MB (presumably this is the runtime overhead), and that importing a package seems to put the entire package inside, regardless of usage.

My actual question was basically if those 2 points are the default behavior or the only behavior? I gave some examples of C programs that are code-wise equivalent to the Go programs, but that I have carefully picked compiler and linker flags for them to avoid linking with any external C runtime (I verified this with Dependency Walker just to be sure). The purpose of the C examples was to show how small the actual code is, and also to show a case where you do need something and you import only what you need.

I actually think that this behavior (put everything inside just in case) is a good setting to have as a default, but I thought that there may be some compiler or linker flag to change this. Now, I do not think that it would be sensible to say you don't want the runtime or parts of it. However, I think that selectively including parts of a package is not such a strange thing to have. Let me explain:

Let's say we are writing in C/C++ and we include a huge header file with tons of functions, but we only use a small portion of them. In this scenario it is possible to end up with an executable that will not contain any unused code from that header file. (Real world example: A math library with support for 2D/3D/4D vectors and matrices, quaternions, etc.. all of which come in 2 version one for 32bit floats and one for 64bit floats + conversions from one to the other)

This is the sort of thing I was looking for. I fully understand that doing this may cause issues in some cases, but still. It's not like Go does not have other things that may cause serious issues.. they have the "unsafe" package, which is there if you need it but it's like "use at your own risk" kinda package.


ORIGINAL QUESTION:

After working with Go (golang) for some time I decided to look into the executable that it produces. I saw that the my project was clocking in at more than 4.5MB for the executable alone, while another project that is similar in complexity and scope, but written in C/C++ (compiled with MSVC) was less than 100KB.

So I decided to try some things out. I've written really stupid and dead simple programs both in C and Go to compare the output. For C I am using MSVC, compiling a 64 bit executable in release mode and NOT linking with the C runtime (as far as I understand it seems to me that Go executable only link with it if using CGO)

First run: A simple endless loop, that's it. No prints, no interaction with the OS, nothing.

C:

#include "windows.h"

int main() 
{
    while (true);
}

void mainCRTStartup()
{
    main();
}

GO:

package main

func main() {
    for {
    }
}

Results:

C : 3KB executable. Depends on nothing

GO: 1,057 KB executable. Depends on 29 procedures from KERNEL32.DLL

There is a huge difference there, but I thought that it might be unfair. So next test I decided to remove the loop and just write a program that immediately returns with an exit code of 13:

C:

    #include "windows.h"

    int main() 
    {
        return 13;
    }

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

GO:

package main

import "os"

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



Results:

C: 4KB executable. Depends on 1 procedure from KERNEL32.DLL

GO: 1,281 KB executable. Depends on 31 procedures from KERNEL32.DLL

It seems that Go executable "bloated". I understand that unlike C, Go puts a considerable amount of it's runtime code into the executable, which is understandable, but it's not enough to explain the sizes.

Also it seems like Go works in a package granularity. What I mean is that it will not cram into the executable packages that you do not use, but if you import a package you get ALL of it, even if you only need a small subset, even if you don't use it at all. For example just importing "fmt" without even calling anything there expands the previous executable from 1,281KB to 1,777 KB.

Am I missing something like some flags to the Go compiler to tell it to be less bloated (I know there are many flags that I can set and also give flags to the native compiler and linker, but I have not found for this specifically) or is it just something no one cares about in 2019 anymore since what are a few megabytes really?

</div>
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享

1条回答 默认 最新

  • 已采纳
    douxuanjie2692 douxuanjie2692 2019-07-19 19:45

    Here are some things that the Go program includes that the C program does not include:

    • Container types, such as hash maps and arrays, and their associated functions

    • Memory allocator, with optimizations for multithreaded programs

    • Concurrent garbage collector

    • Types and functions for threading, such as mutexes, condition variables, channels, and threads

    • Debugging tools like stack trace dumping and the SIGQUIT handler

    • Reflection code

    (If you are curious exactly what is included, you can look at the symbols in your binary with debugging tools. On macOS and Linux you can use nm to dump the symbols in your program.)

    The thing is—most Go programs use all of these features! It’s hard to imagine a Go program that doesn't use the garbage collector. So the creators of Go have not created a special way to remove this code from programs—since nobody needs this feature. After all, do you really care how big "Hello, world!" is? No, you don’t.

    From the FAQ Why is my trivial program such a large binary?

    The linker in the gc toolchain creates statically-linked binaries by default. All Go binaries therefore include the Go runtime, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.

    Also keep in mind that if you are compiling on Windows with MSVC, you may be using a DLL runtime, such as MSVCR120.DLL... which is about 1 MB.

    点赞 评论 复制链接分享

相关推荐