4

I created a Haskell CLI with the Stack tool. I've just successfully set up cross-compilation thanks to Travis, but I don't understand why the executable size is so different between linux (6MB), osx (2MB) and windows (18MB!). How come?

Release: https://github.com/unfog-io/unfog-cli/releases/tag/v0.1.2

Travis conf: https://github.com/unfog-io/unfog-cli/blob/master/.travis.yml

EDIT

When I compress executables with tar.gz, I reduce the difference, but still! I have now linux (1.35MB), osx (0.61MB), windows (3.93MB) (see release)

soywod
  • 4,377
  • 3
  • 26
  • 47
  • I don't know about this too much, but there are some question regarding this. Give it a look. It might help https://stackoverflow.com/questions/12719207/why-are-haskell-ghc-executables-so-large-in-filesize – lsmor Nov 26 '19 at 09:56
  • Thanks for the link, I will check, but it seems to solve more the question "why is it so big". My point is more about "why size so different between OS". – soywod Nov 26 '19 at 13:27

1 Answers1

3

The difference between the Linux and MacOS builds is probably due to something called "split sections".

Enabling the -split-sections GHC flag on Linux puts each compiled function into its own linker section (instead of the historical approach of placing all functions into a single ".text" section). This allows the linker to drop code that isn't used with a granularity that isn't really possible otherwise.

Unfortunately, all dependencies need to be built with this flag in order to benefit. You can force Stack to rebuild everything properly by adding the following lines to your project's stack.yaml file:

ghc-options:
    "$everything": -split-sections

This method of specifying GHC options for dependencies is documented here.

If you rebuild your unfog with this change, it'll actually rebuild "base" and "vector" and everything else from scratch, so it might take a while. But, the resulting binary makes it worth it. Unstripped, its size drops from about 11Meg to 4Meg, and if you strip it, it's only:

-rwxr-xr-x 1 buhr buhr 1494696 Nov 27 19:40 unfog

which is even smaller than the MacOS version you posted.

Now, as I understand it, the reason the original MacOS version is only 2Meg is that the MacOS linker already implements something similar to split sections. I'm not sure if also enabling -split-sections for the MacOS build might give a additional gain, or if -split-sections is an automatic default under MacOS. Anyway, it can't hurt to try it out.

For Windows, the main reason it's so gigantic is that the MinGW GCC toolchain is used to compile Windows binaries, so there's an entire compatibility layer of GNU-ish libraries (libc, libm, libpthread, libgmp, etc.), and -- unlike with the Linux and MacOS builds -- they all get statically linked into the Windows binary. The only dynamic linking for Windows is to standard Windows DLLs.

Note that -split-sections might or might not work on Windows. There are some comments on the bug tracker that make it unclear. Anyway, it might be worth trying out to see if it makes a difference.

Some additional references:

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71