Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does using LTO increase the size of my Rust binary?

Introduction

I finished a small Rust project (about 300 lines of code) with the following dependencies:

  • rumqtt
  • signal
  • log
  • env_logger

Problem

When using cargo build --release without further configuration, a 2.942.744 bytes (= 2,8 MiB) binary is generated. I tried to optimize this by enabling Link Time Optimization (LTO) in my Cargo.toml:

[profile.release]
lto = true

To my surprise, the binary grows, with a new size of 3.848.288 bytes (= 3,7 MiB).

How can this be explained? Is there any mistake I made configuring Cargo?

like image 707
PEAR Avatar asked Sep 12 '18 08:09

PEAR


People also ask

Why is my Rust binary so large?

Rust uses monomorphization and static linking, which does tend to cause the size of binaries to be larger than languages which do not do those things, however the benefits of these techniques are generally seen as outweighing the file size impact.

Does LTO improve performance?

LTO provides a performance boost for all the compilers. With small projects, this boost probably wouldn't be noticeable, but for big ones this option definitely makes a difference.

How do you make a binary smaller in Rust?

Enable Link Time Optimization (LTO) By default, Cargo instructs compilation units to be compiled and optimized in isolation. LTO instructs the linker to optimize at the link stage. This can, for example, remove dead code and often times reduces binary size.

Are Rust binaries big?

By default, Rust produces fairly large binaries, which may be annoying when building a RAT. A larger executable means more resources used on the system, longer and less reliable downloads, and easier to be detected. We will see a few tips to reduce the size of a Rust executable.


1 Answers

What is LTO?

LTO means Link-Time Optimization. It is generally set up to use the regular optimization passes used to produce object files... at link time instead, or in addition.

Why does it matter?

A compiler does not inherently optimize for speed over size or size over speed; and therefore neither does LTO.

Instead, when invoking the compiler, the user selects a profile. For rustc:

  • O0, O1, O2 and O3 are optimizing for speed.
  • Os and Oz are optimizing for size.

LTO can be combined on top of any optimization level, and will follow the selected profile.

So why did the size increase?

By default, the [release] profile instructs cargo to invoke rustc with O2 or O3, which attempts to optimize for speed over size.

In particular, O3 can rely quite heavily on inlining. Inlining is all about giving more context to the optimizer, and therefore more optimization opportunities... LTO offers more chances to apply inlining (more known functions), and here it appears that more inlining happened.

So why did this blog post claim it reduced size?

It also reduces size. Possibly.

By giving more context, the optimizer/linker can realize that some portion of the code or dependencies are not used at all, and therefore can be elided.

If using Os or Oz, the size is near certain to go down.

If using O2 or O3, unused code is removed while inlining adds more code, so it's quite unpredictable whether the end result is bigger or smaller.

So, LTO?

LTO gives the optimizer a better opportunity at optimizing, so it's a good default for Releases.

Just remember that cargo leans toward speed over size by default, and if this does not suit you, you may want to select another optimization direction.

like image 185
Matthieu M. Avatar answered Oct 02 '22 23:10

Matthieu M.