What is the smallest unit for static linking?

#4

A Rust .rlib crate is more like a C .a archive. The linker is responsible for discarding unused sections. Could you be more specific about which specific symbols you’re seeing in a compiled executable that shouldn’t be there?

1 Like

#5

You seem to mean that the section is the smallest unit for linking. That is a very good news for me, as the section is even smaller than a module.

0 Likes

#6

I really what the rust core team to address this issue. Unnecessarily huge executable size is unacceptable to many programming purists, and purists are the most likely users of rust. This is a big shame! See also the following page:

https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html

0 Likes

#7

If I were to statically link a similar C++ program I’d actually end up with an executable with a similar size, so I’m not exactly sure what your point is…

A section isn’t really a unit for linking, trimming unused sections is the machine code equivalent of the dead code elimination that normally happens.

I’ve never found executable size to be much of an issue. At work we distribute my Rust code as a DLL alongside our main product and despite the project being fairly decently sized (we’ve written tens of thousands of lines, pulling in hundreds of thousands via crates.io) the DLL is still only 1-2MB when compiled in release mode.

3 Likes

#8

The size of executable is less wasteful if you make fuller use of a crate. So this is really not a big issue for most commercial products. But for classroom exercises such as the hello world program this really is an issue, and I think purists (including me) will be unhappy with it.

0 Likes

#9

Most of the overhead from hello world is because you pull in all the standard library stuff for string formatting and working with io. That’s not really avoidable without jumping through a lot of hoops.

If you really care about making a small executable you can always tell rust to link dynamically where possible. The downside to this is you need to have the Rust standard library on your system in order to run the program. C and C++ can do this just fine because libc is installed everywhere, however Rust doesn’t have this luxury and if we didn’t statically link (causing larger binaries) people would complain that it generates “broken” executables… You just can’t win :disappointed:

If you were to ask rustc to prefer dynamic linking then the executable size is comparable to the equivalent C program.

$ cat main.rs
fn main() {
    println!("Hello World!");
}
$ cat main.c 
#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("Hello World!");
    return 0;
}
$ rustc main.rs -O -o main_release                  
$ rustc main.rs -O -o main_dynamic -C prefer-dynamic
$ gcc main.c -o main_c
$ du -h main_*
12K	main_c
16K	main_dynamic
5.0M	main_release
7 Likes

#10

Sadly, Rust is optimised for real world use-cases. We support trimming down your executable to your hearts intent, but the default is reasonable for general programs.

Also, the number of purists is in general quite low, it would be harmful to tune the default settings towards them.

For example, Rust - by default - uses an allocator that is purist-unfriendly. jemalloc.

We are happy to support these usecases, though, Rust is even used in the demo scene.

I really what the rust core team to address this issue. Unnecessarily huge executable size is unacceptable to many programming purists, and purists are the most likely users of rust.

I’m sorry, but we hate gut feeling, so we measure. Your statement doesn’t match numbers at all. https://blog.rust-lang.org/2016/06/30/State-of-Rust-Survey-2016.html

Scroll down to “what programming language are you most comfortable with”. If you do a tally, the number of C and C++ programmers isn’t even half of the community and purists are a subgroup of them.

12 Likes

#11

Dear Skade, thank you for your information! Could you please tell me why the crate must be the smallest linking unit? There are three possibilities:

  1. Using C-style obj as smallest linking unit in rust is in principle not in contradiction with other performance considerations, but because the rust team is too busy doing other more important things. This issue will be addressed if those more important things have been fixed.

  2. Using C-style obj as smallest linking unit in rust is in principle not in contradiction with other performance considerations, but it is very difficult to find a perfect solution so that both purists and practitioners can be happy, and the rust team will therefore not seek that kind of a solution in the foreseeable future.

  3. It is theoretically impossible to have C-style obj as smallest linking unit, while not hurting performance in other aspects.

Which of the above three is correct? Thank you in advance!

0 Likes

#12

I think your dot points are probably too absolute and the real answer will probably be somewhere in the middle, or maybe something completely different. Instead, like all engineering tasks it’s all about trade offs and finding something which suits your needs.

Using the crate as the base compilation unit probably provides a happy medium between not having enough context (C-style one object per source file, requires headers and other ugliness) and lumping a crate with all its dependencies into one massive object file (bloated binaries, massive compilation times etc).

Incremental compilation also changes everything because the the compiler can dynamically choose to break a crate up into an an arbitrary number of smaller object files to make builds more parallel. With incremental compilation the idea is if only one module (or function, or whatever) changes we only need to recompile that bit of code. I would definitely recommend you read the RFC for more!

Given that, stating that you must either take a one-object-per-source-file or a one-object-per-crate approach is probably too narrow minded.

So I guess you could say:

  • There’s nothing stopping Rust from using lots of small object files
  • The rust developers are actively working on this (incremental compilation, etc)
  • Solutions exist

I should also mention that in real life you never actually deal with individual object files anyway. rustc and cargo exist to automate the build process and figure out the best way to do things.

6 Likes

#13

Have you tried using LTO? This will mitigate the fact that the compilation unit is an entire crate because it allows the linker to strip out unused stuff at link time.

If you use Xargo you can even perform LTO on std because Xargo uses the source for std when it performs a build of your project!

5 Likes

#14

Thank you Michael! Your answer is really one I have been looking for.

Rust is the final stop of the evolution of computer languages. It is still young, still on its way toward perfection, and we need patience.

0 Likes

#15

Thank you John! I am now looking into Xargo. LTO may be a promising solution.

0 Likes

#16

You can also specifiy opt-level = 'z' when using a nightly compiler to instruct rustc to optimize for size instead of performance if binary size is of more concern.

Finally, if you are compiling on Linux or Mac, you may want to remove jemalloc (~200-300K).

1 Like

#17

Isn’t that the missing bullet holes problem? The low number of “purist” programmers responding to the survey may as well be exactly the symptom of turning them off from Rust.

0 Likes

#18

The continued references to “purity” in this thread are disconcerting. It is completely undefined, and liable to be redefined during any given conversation to serve the user’s biases.

I would encourage people to use more precise language when talking about what they’re looking for. “purity” only seems to serve the purpose of making one group of people feel superior to others.

3 Likes

#19

I agree. In addition to being generally vague/problematic, “pure” is a word which I most frequently hear from functional programmers with a totally different meaning, so the way it is used here is especially confusing.

1 Like

#20

No, sorry. The missing bullet hole problem was a nice metaphor applied to a very specific problem in that blog post, but you can’t just throw it at any statistic.

First, as others say, “engine” is a very defined term, “embedded” isn’t quite, but has enough boundaries. As other mentioned here, “purist” is a fuzzy term that matches many descriptions. Also, the point of me invoking this statistic is also that Rust has a lot of draw outside of its classic field. Second, “embedded” is a much more important field than “purism” is. Third, Rust supports some notion “purism” (through a couple of flags), while the blog post was about Rust having problems in the embedded space that make it not usable to some users.

Yes, I liked appreciated the blog post, I’d hate if I see it used to undermine any statistic.

1 Like

#21

There are several post about executable size in this forum already. Also there are several cases that has shown that Rust scales down to small executable if you want. https://www.youtube.com/watch?v=GjuridCR2Fo this is made with Rust. The executable is 64k in size including all music, graphics, etc and static linking.

Sure you will need to do some effort to get to something like this but as shown this is doable.

2 Likes

#22

There’s some confusion here.

A crate is Rust’s unit of compilation in the sense that rustc will always be invoked on an entire crate. At least historically speaking, that meant it would always recompile the whole thing from scratch, which was a real pitfall for compilation speed, but incremental compilation is starting to change that.

That does not mean that rustc will always bundle all of a crate’s code into a final executable. In some cases rustc can eliminate dead code itself, but anything that makes it to the linking stage will be eliminated by the linker. On GNU targets, rustc passes the equivalent of -ffunction-sections as an LLVM option, and passes --gc-sections to the linker: each function goes in its own section, and the linker is told to ‘garbage collect’ unreferenced sections. On macOS, the linker is able to garbage collect symbols without needing separate sections, and rustc passes -dead_strip to tell it to do so. I’m not sure what happens on Windows, but it’s probably similar.

Rust does tend to have issues with binary bloat – for various reasons which hopefully someone else can explain, since I’m too tired right now to try – but those reasons are entirely unrelated to crates being the unit of compilation.

LTO can help with binary size, but not because it uses a smaller unit of compilation; on the contrary, by deferring all native code generation and optimization until link time, it essentially makes the “unit of compilation” the entire program!

10 Likes

#23

pc-windows-msvc is by far the best in this regard because link.exe not only will strip unreferenced sections (each individual function and static is a separate section), but it will even perform identical code folding resulting in size reductions to the point where LTO has no effect on binary size. Also the fact that pc-windows-msvc doesn’t statically link in any C libraries like jemalloc or libbacktrace helps significantly with binary size.

6 Likes