馃 How to minimize Rust binary size 馃摝

Related tags

Filesystem rust
Overview

Minimizing Rust Binary Size

GitHub Actions

To help this project reach more users, consider upvoting the min-sized-rust Stack Overflow answer.

This repository demonstrates how to minimize the size of a Rust binary.

By default, Rust optimizes for execution speed rather than binary size, since for the vast majority of applications this is ideal. But for situations where a developer wants to optimize for binary size instead, Rust provides mechanisms to accomplish this.

Build in Release Mode

Minimum Rust: 1.0

By default, cargo build builds the Rust binary in debug mode. Debug mode disables many optimizations, which helps debuggers (and IDEs that run them) provide a better debugging experience. Debug binaries can be 30% or more larger than release binaries.

To minimize binary size, build in release mode:

$ cargo build --release

strip Symbols from Binary

OS: *nix

Note: Looking for a tool to help automated striping? Check out cargo-strip or follow Cargo #3483.

See also: sstrip a small utility that removes a few bytes from an executable that strip leaves behind. sstrip should be run after strip.

By default on Linux and macOS, symbol information is included in the compiled .elf file. This information is not needed to properly execute the binary. To remove this, run strip on the .elf file:

$ strip target/release/min-sized-rust

Available starting 1.45.0-nightly (2020-05-28), Cargo has strip functionality built in:

Minimum Rust: Nightly

Modify Cargo.toml in this way:

cargo-features = ["strip"]

[profile.release]
strip = true  # Automatically strip symbols from the binary.

Optimize For Size

Minimum Rust: 1.28

Cargo defaults its optimization level to 3 for release builds, which optimizes the binary for speed. To instruct Cargo to optimize for minimal binary size, use the z optimization level in Cargo.toml:

[profile.release]
opt-level = "z"  # Optimize for size.

Enable Link Time Optimization (LTO)

Minimum Rust: 1.0

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.

Enable LTO in Cargo.toml:

[profile.release]
lto = true

Remove Jemalloc

Minimum Rust: 1.28 Maximum Rust: 1.31

As of Rust 1.32, jemalloc is removed by default. If using Rust 1.32 or newer, no action is needed to reduce binary size regarding this feature.

Prior to Rust 1.32, to improve performance on some platforms Rust bundled jemalloc, an allocator that often outperforms the default system allocator. Bundling jemalloc added around 200KB to the resulting binary, however.

To remove jemalloc on Rust 1.28 - Rust 1.31, add this code to the top of main.rs:

use std::alloc::System;

#[global_allocator]
static A: System = System;

Reduce Parallel Code Generation Units to Increase Optimization

By default, Cargo specifies 16 parallel codegen units for release builds. This improves compile times, but prevents some optimizations.

Set this to 1 in Cargo.toml to allow for maximum size reduction optimizations:

[profile.release]
codegen-units = 1

Abort on Panic

Minimum Rust: 1.10

Note: Up to this point, the features discussed to reduce binary size did not have an impact on the behaviour of the program (only its execution speed). This feature does have an impact on behavior.

By default, when Rust code encounters a situation when it must call panic!(), it unwinds the stack and produces a helpful backtrace. The unwinding code, however, does require extra binary size. rustc can be instructed to abort immediately rather than unwind, which removes the need for this extra unwinding code.

Enable this in Cargo.toml:

[profile.release]
panic = "abort"

Optimize libstd with build-std

Minimum Rust: Nightly

Note: See also Xargo, the predecessor to build-std. Xargo is currently in maintenance status.

Example project is located in the build_std folder.

Rust ships pre-built copies of the standard library (libstd) with its toolchains. This means that developers don't need to build libstd every time they build their applications. libstd is statically linked into the binary instead.

While this is very convenient there are several drawbacks if a developer is trying to aggressively optimize for size.

  1. The prebuilt libstd is optimized for speed, not size.

  2. It's not possible to remove portions of libstd that are not used in a particular application (e.g. LTO and panic behaviour).

This is where build-std comes in. The build-std feature is able to compile libstd with your application from the source. It does this with the rust-src component that rustup conveniently provides.

Install the appropriate toolchain and the rust-src component:

$ rustup toolchain install nightly
$ rustup component add rust-src --toolchain nightly

Build using build-std:

# Find your host's target triple. 
$ rustc -vV
...
host: x86_64-apple-darwin

# Use that target triple when building with build-std.
# Add the =std,panic_abort to the option to make panic = "abort" Cargo.toml option work.
# See: https://github.com/rust-lang/wg-cargo-std-aware/issues/56
$ cargo +nightly build -Z build-std=std,panic_abort --target x86_64-apple-darwin --release

Remember to strip the resulting executable. On macOS, the final binary size is reduced to 51KB.

Remove panic String Formatting with panic_immediate_abort

Minimum Rust: Nightly

Even if panic = "abort" is specified in Cargo.toml, rustc will still include panic strings and formatting code in final binary by default. An unstable panic_immediate_abort feature has been merged into the nightly rustc compiler to address this.

To use this, repeat the instructions above to use build-std, but also pass the following -Z build-std-features=panic_immediate_abort option.

$ cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
    --target x86_64-apple-darwin --release

Remember to strip the resulting executable. On macOS, the final binary size is reduced to 30KB.

Remove core::fmt with #![no_main] and Careful Usage of libstd

Minimum Rust: Nightly

Example project is located in the no_main folder.

This section was contributed in part by @vi

Up until this point, we haven't restricted what utilities we used from libstd. In this section we will restrict our usage of libstd in order to reduce binary size further.

If you want an executable smaller than 20 kilobytes, Rust's string formatting code, core::fmt must be removed. panic_immediate_abort only removes some usages of this code. There is a lot of other code that uses formatting in some cases. That includes Rust's "pre-main" code in libstd.

By using a C entry point (by adding the #![no_main] attribute) , managing stdio manually, and carefully analyzing which chunks of code you or your dependencies include, you can sometimes make use of libstd while avoiding bloated core::fmt.

Expect the code to be hacky and unportable, with more unsafe{}s than usual. It feels like no_std, but with libstd.

Start with an empty executable, ensure xargo bloat --release --target=... contains no core::fmt or something about padding. Add (uncomment) a little bit. See that xargo bloat now reports drastically more. Review source code that you've just added. Probably some external crate or a new libstd function is used. Recurse into that with your review process (it requires [replace] Cargo dependencies and maybe digging in libstd), find out why it weighs more than it should. Choose alternative way or patch dependencies to avoid unnecessary features. Uncomment a bit more of your code, debug exploded size with xargo bloat and so on.

On macOS, the final stripped binary is reduced to 8KB.

Removing libstd with #![no_std]

Minimum Rust: 1.30

Example project is located in the no_std folder.

Up until this point, our application was using the Rust standard library, libstd. libstd provides many convenient, well tested cross-platform APIs and data types. But if a user wants to reduce binary size to an equivalent C program size, it is possible to depend only on libc.

It's important to understand that there are many drawbacks to this approach. For one, you'll likely need to write a lot of unsafe code and lose access to a majority of Rust crates that depend on libstd. Nevertheless, it is one (albeit extreme) option to reducing binary size.

A striped binary built this way is around 8KB.

#![no_std]
#![no_main]

extern crate libc;

#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
    // Since we are passing a C string the final null character is mandatory.
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe {
        libc::printf(HELLO.as_ptr() as *const _);
    }
    0
}

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Compress the binary

Up until this point, all size-reducing techniques were Rust-specific. This section describes a language-agnostic binary packing tool that is an option to reduce binary size further.

UPX is a powerful tool for creating a self contained, compressed binary with no addition runtime requirements. It claims to typically reduce binary size by 50-70%, but the actual result depends on your executable.

$ upx --best --lzma target/release/min-sized-rust

It should be noted that there have been times that UPX-packed binaries have flagged heuristic-based anti-virus software because malware often uses UPX.

Tools

  • cargo-bloat - Find out what takes most of the space in your executable.
  • momo - proc_macro crate to help keeping the code footprint of generic methods in check.
  • Twiggy - A code size profiler for Wasm.

Containers

Sometimes it's advantageous to deploy Rust into containers (e.g. Docker). There are several great existing resources to help create minimum sized Docker containers that run Rust binaries.

References

Comments
  • 1.45.0 is now stable :)

    1.45.0 is now stable :)

    Thanks for this great repo!

    Just wanted to point out that in the strip Symbols from Binary section, it mentions nightly 1.45.0, however, this is now a stable version.

    opened by h5rdly 6
  • Shall there be another section after panic_immediate_abort, but before no_std?

    Shall there be another section after panic_immediate_abort, but before no_std?

    In addition to panic_immediate_abort it should use !#[no_main] and avoid println!.

    This allows actually excluding formatting code while also using some of libstd's features.

    opened by vi 5
  • Custom ld script

    Custom ld script

    I created a custom ld script that makes elf64 binary smaller. It is possible to add this script to build sequence by creating build.rs file:

    fn main() {
    	println!("cargo:rustc-link-arg=-Wl,-T,elf64-min.ld");
    }
    

    Here is elf64-min.ld:

    OUTPUT_FORMAT("elf64-x86-64")
    ENTRY(_start)
    PHDRS {
    	text PT_LOAD FILEHDR PHDRS;
    	/*tls PT_TLS;*/ /* uncomment this if you use std or pthreads */
    	interp PT_INTERP;
    	dynamic PT_DYNAMIC;
    }
    
    SECTIONS
    {
    	. = SIZEOF_HEADERS;
    	t : {
    		KEEP (*(.init))
    		*(.text*)
    		KEEP (*(.fini))
    		*(.rodata*)
    		*(.data*)
    		*(.bss*)
    	} :text
    	.init_array : {
    		PROVIDE_HIDDEN (__init_array_start = .);
    		KEEP (*(SORT_BY_INIT_PRIORITY(.init_array*)))
    		PROVIDE_HIDDEN (__init_array_end = .);
    	} :text
    	.rel : { *(.data.rel*) } :text
    
    	td : { *(.tdata*) } :text :tls
    	tb : { *(.tbss*) } :text :tls
    
    	i : {
    		*(.interp)
    	} :text :interp
    
    	d : {
    		*(.dynamic)
    		*(.got)
    		*(.got.*)
    		*(.gnu.*)
    		*(.dynsym)
    	} :dynamic :text
    
    	/DISCARD/ : { *(.note*) }
    }
    
    opened by cbytensky 4
  • Alternative strip: llvm-strip via cargo-binutils

    Alternative strip: llvm-strip via cargo-binutils

    Consider adding cargo-binutils to the README. It provides easy to use cargo commands for a few llvm tools that can be installed via rustup. These tools work for quite a few platforms including windows and is easy to get setup if you use rustup for your rust toolchain.

    $ rustup component add llvm-tools-preview
    $ cargo install cargo-binutils
    $ cargo strip
    

    The cargo-binutils cargo commands automatically handle building the project if needed as well as finding and passing the target binary path to the tool. They also support the majority of the command line options that cargo build does to make the tools as intuitive as using cargo itself.

    Also provides access to llvm-size via cargo size.

    https://github.com/rust-embedded/cargo-binutils

    Disclaimer: I recently did a little bit of work on cargo-binutils to make it easier to use and more closely match the built in cargo commands, and the README is a little out of date because of that.

    opened by ZeroErrors 4
  • Setup profile.dev to match profile.release panic behavior.

    Setup profile.dev to match profile.release panic behavior.

    It might be useful to have a [profile.dev] section which has matching panic behavior for the xargo build (unless the idea was to demonstrate both kinds of panic).

    opened by drmikehenry 4
  • Add `strip=true` to the repo examples

    Add `strip=true` to the repo examples

    Enables strip in the example Cargo.toml files in this repository, per the recommendation now in README after #34.

    The reminder to strip has been removed from the README, since the examples will now do that automatically.

    opened by edmorley 3
  • Unable to strip information

    Unable to strip information

    Running $ cargo +nightly build -Z strip=symbols on Windows I get this error message:

    error: unknown `-Z` flag specified: strip
    

    I just updated my nightly toolchain still the same error. Any idea what this may be happening?

    bug 
    opened by Joe23232 3
  • Mention sstrip

    Mention sstrip

    For the sake of completeness, it'd probably be a good idea to mention the sstrip utility from ELFkickers.

    As this mirror explains:

    Most ELF executables are built with both a program header table and a section header table. However, only the former is required in order for the OS to load, link and execute a program. sstrip attempts to extract the ELF header, the program header table, and its contents, leaving everything else in the bit bucket. It can only remove parts of the file that occur at the end, after the parts to be saved. However, this almost always includes the section header table, along with a few other sections that are not involved in program loading and execution.

    It should be noted that most programs that work with ELF files are dependent on the section header table as an index to the file's contents. Thus, utilities such as gdb and objdump will often have limited functionality when working with an executable with no section header table. Some other utilities may refuse to work with them at all.

    Basically, it's a tool primarily used for things like saving flash memory on embedded Linux devices which will shave a few more bytes off what the regular strip utility produces.

    enhancement 
    opened by ssokolow 3
  • Explain better for speed at runtime

    Explain better for speed at runtime

    I read both:

    • https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level
    • https://github.com/johnthagen/min-sized-rust#optimize-for-size

    What I don't understand is which speed gain I'm changing if I use:

    [profile.release]
    opt-level = "z"
    

    instead of:

    [profile.release]
    opt-level = 3
    
    • Is it right that today opt-level = 3 is the best setting (for opt-level section) for runtime speed?

    • If I instead use opt-level = "z" I'm decreasing runtime performance, right?

    I'm not interested in building/compiling speed.

    opened by frederikhors 3
  • cargo-strip

    cargo-strip

    Thanks a lot for this repository. It contains a lot of useful information. I found that stripping cargo generated binaries is a bit painful as you need to identify their names manually.

    To simplify the process, I released cargo-strip over the week-end that automates this. It could be improved but works fine for my needs.

    enhancement 
    opened by guedou 3
  • Add note about cross compiling via nightly

    Add note about cross compiling via nightly

    It is possible to cross compile w/ the +nightly targets.

    ex.

    rustup +nightly target add x86_64-unknown-linux-musl
    cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
        --target x86_64-unknown-linux-musl --release
    

    Will work, this can be added as an addendum to the Optimize libstd and Remove Panic String sections.

    Discovered from: https://github.com/rust-lang/wg-cargo-std-aware/issues/76

    opened by Noxsios 2
  • Identical code folding

    Identical code folding

    Since Rust can't do polymorphization properly yet, using generics generates a lot of duplicated functions because of monomorphization. These functions take space in the binary, even though they have completely the same instructions.

    Some linkers (gold, lld) can deduplicate these identical functions using Identical Code Folding, and thus reduce binary size (and potentially also improve usage of the i-cache).

    You can specify this linker option using a linker flag, for example like this:

    $ RUSTFLAGS="-Clink-args=-fuse-ld=lld -Clink-args=-Wl,--icf=all" cargo build
    

    I measured the binary size change for the following program:

    fn foo() {
        let mut a: Vec<u8> = vec![1, 2, 3];
        a.pop();
    
        let mut b: Vec<u32> = vec![1, 2, 3];
        b.pop();
    
        let mut c: Vec<i32> = vec![1, 2, 3];
        c.pop();
    }
    
    fn main() {
        foo();
    }
    

    Here are binary sizes are after running strip on them:

    | Linker | Mode | Binary size (B) | ICF (Identical Code Folding) | |--------|---------|-----------------|------------------------------| | gold | debug | 342696 | No | | gold | debug | 330408 | Yes | | gold | release | 322216 | No | | gold | release | 318120 | Yes | | lld | debug | 330968 | No | | lld | debug | 321840 | Yes | | lld | release | 310616 | No | | lld | release | 306848 | Yes |

    opened by Kobzol 2
  • Add note about monomorphisation

    Add note about monomorphisation

    Generics with static dispatch can lead to duplicated functions, and if they are big enough, it will make for a large piece of the final binary.

    Alternatives are dynamic dispatch with dyn, or just removing generic code.

    Is seems very important for minimizing binary size.

    (If you don't want to change the function API, and the function is being generic over something like AsRef<Path>, you can also create helper function which resolves path.as_ref() and calls a non-generic function that receives &Path instead.)

    enhancement 
    opened by marcospb19 5
  • Add section with alloc only but not libstd

    Add section with alloc only but not libstd

    When https://github.com/rust-lang/rfcs/pull/2480 is stabilized and implemented, it should be possible to create binaries that don't depend on std but do depend on alloc. This would be a step between no_std and no_main, because at least you could use the heap. The actual sample hello world code would probably not change much. It would probably be best to show off using a Vec in this instance.

    This could probably be added now as a nightly subproject.

    enhancement 
    opened by johnthagen 0
Owner
null
Minty is an amazingly fast file deduplication app built in rust with a rust user interface.

minty Project Minty has a new look and feel!!! Minty is an amazingly fast file deduplication app built in rust with a rust user interface. I say super

null 26 Nov 20, 2022
High level FFI binding around the sys mount & umount2 calls, for Rust

sys-mount High level FFI bindings to the mount and umount2 system calls, for Rust. Examples Mount This is how the mount command could be written with

Pop!_OS 31 Dec 9, 2022
ergonomic paths and files in rust

path_abs: ergonomic paths and files in rust. This library aims to provide ergonomic path and file operations to rust with reasonable performance. See

Rett Berg 45 Oct 29, 2022
Temporary directory management for Rust

tempdir A Rust library for creating a temporary directory and deleting its entire contents when the directory is dropped. Documentation Deprecation No

null 132 Jan 7, 2023
Temporary file library for rust

tempfile A secure, cross-platform, temporary file library for Rust. In addition to creating temporary files, this library also allows users to securel

Steven Allen 782 Dec 27, 2022
Extended attribute library for rust.

xattr A small library for setting, getting, and listing extended attributes. Supported Platforms: Linux, MacOS, FreeBSD, and NetBSD. API Documentation

Steven Allen 33 Nov 12, 2022
Rust implemention of Ascon

Ascon Pure Rust implementation of the lightweight Authenticated Encryption and Associated Data (AEAD) Ascon-128 and Ascon-128a. Security Notes This cr

Sebastian Ramacher 4 May 28, 2022
Lightweight Google Cloud Storage sync Rust Client with better performance than gsutil rsync

gcs-rsync Lightweight and efficient Rust gcs rsync for Google Cloud Storage. gcs-sync is faster than gsutil rsync when files change a lot while perfor

@cboudereau 4 Sep 8, 2022
A POSIX select I/O Multiplexing Rust library.

A POSIX select I/O Multiplexing Rust library.

b23r0 4 Jul 6, 2022
Supertag is a tag-based filesystem, written in Rust, for Linux and MacOS

Supertag is a tag-based filesystem, written in Rust, for Linux and MacOS. It provides a tag-based view of your files by removing the hierarchy constraints typically imposed on files and folders. In other words, it allows you to think about your files not as objects stored in folders, but as objects that can be filtered by folders.

Andrew Moffat 539 Dec 24, 2022
Spacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust.

Spacedrive A file explorer from the future. spacedrive.com 禄 Download for macOS 路 Windows 路 Linux 路 iOS 路 watchOS 路 Android ~ Links will be added once

Spacedrive 16.2k Jan 7, 2023
Rust+Cargo lightweight hello world with the most minimum binary size possible.

Lightweight Cargo Hello World Rust+Cargo lightweight hello world with the most minimum binary size possible. requirements 1: Rustup (Rustc, Cargo) Ins

Raymond 1 Dec 13, 2021
A tool for analyzing the size of dependencies in compiled Golang binary files, providing insights into their impact on the final build.

gsv A simple tool to view the size of a Go compiled binary. Build on top of bloaty. Usage First, you need to compile your Go program with the followin

null 70 Apr 12, 2023
Binary coverage tool without binary modification for Windows

Summary Mesos is a tool to gather binary code coverage on all user-land Windows targets without need for source or recompilation. It also provides an

null 384 Dec 30, 2022
Binary coverage tool without binary modification for Windows

Summary Mesos is a tool to gather binary code coverage on all user-land Windows targets without need for source or recompilation. It also provides an

null 381 Dec 22, 2022
Rusty Shellcode Reflective DLL Injection (sRDI) - A small reflective loader in Rust 4KB in size for generating position-independent code (PIC) in Rust.

Shellcode Reflective DLL Injection (sRDI) Shellcode reflective DLL injection (sRDI) is a process injection technique that allows us to convert a given

null 242 Jul 5, 2023
concat-arrays: a rust macro for concatenating fixed-size arrays

concat-arrays: a rust macro for concatenating fixed-size arrays This crate defines concat_arrays!, a macro that allows you to concatenate arrays.

Andrew Cann 5 May 25, 2021
SegVec data structure for rust. Similar to Vec, but allocates memory in chunks of increasing size.

segvec This crate provides the SegVec data structure. It is similar to Vec, but allocates memory in chunks of increasing size, referred to as "segment

Jacob Ryan McCollum 30 Dec 16, 2022
Rust library to get image size and format without loading/decoding

imageinfo-rs Rust library to get image size and format without loading/decoding. The imageinfo don't get image format by file ext name, but infer by f

xiaozhuai, Weihang Ding 47 Dec 30, 2022