← Back to context

Comment by AlexeyBrin

6 months ago

Mostly because of static linking. C and C++ don't put every library they need in the binary by default. The advantage is that a pure Go or Rust binary just works (most of the time) when copied from one machine to another, you don't have to care about installing other libraries.

That’s a great point.

Another advantage is that at least for Rust you can do whole program optimization. The entire program tree is run through the optimizer resulting in all kinds of optimizations that are otherwise impossible.

The only other kinds of systems that can optimize this way are higher level JIT runtimes like the JVM and CLR. These can treat all code in the VM as a unit and optimize across everything.

  •   C++ has had whole program optimization since forever. And you can use static linking if you want, the same as Rust.

  • > Another advantage is that at least for Rust you can do whole program optimization. The entire program tree is run through the optimizer resulting in all kinds of optimizations that are otherwise impossible.

    I get why this might lead to big intermediate files, but why do the final binaries get so big?

    • Rust binaries + all their dynamic libraries are the same size as C++ binaries + their linked libraries (when stripped, this isn't default in Rust)

      The main issue is that Rust binaries typically only link to libc whereas C++ binaries link to everthing under the sun, making the actual executable look tiny because that's not where most of the code lives.

    • Both C++ and Rust are based on monomorphization, which means generic programming is based on a expansion of code for each combination of types. This makes compilation slow and causes code bloat. One then needs whole program optimization to get this under control to some degree.

Go especially, on some platforms they go straight to syscalls and bypass libc entirely. They even bring their own network stack. It's the maximalist plan 9 philosophy in action.

  • I don't really like Go as a language, but this decision to skip libc and go directly with syscalls is genius. I wish Rust could do the same. More languages should skip libc. Glibc is the main reason Linux software is binary non-portable between distros (of course not the only reason, but most of the problems come from glibc).

    • > Glibc is the main reason Linux software is binary non-portable between distros

      Linux software is binary portable between distros as long as the binary was compiled using a Glibc version that is either the same or older than the distros you are trying to target. The lack of "portability" is because of symbol versioning so that the library can expose different versions of the same symbol, exactly so that it can preserve backwards compatibility without breaking working programs.

      And this is not unique to Glibc, other libraries do the same thing too.

      The solution is to build your software in the minimum version of libraries you are supposed to support. Nowadays with docker you can set it up in a matter of minutes (and automate it with a dockerfile) - e.g. you can use -say- Ubuntu 22 to build your program and it'll work in most modern Linux OSes (or at least glibc wont be the problem if it doesn't).

      2 replies →