DEV Community

阿豪
阿豪

Posted on

I shrunk my Rust binary from 11MB to 4.5MB with bloaty-metafile

TL;DR: Used bloaty-metafile to analyze binary size, disabled default features on key dependencies, reduced size by 59% (11MB → 4.5MB)

The Problem

I've been working on easy-install (ei), a CLI tool that automatically downloads and installs binaries from GitHub releases based on your OS and architecture. Think of it like a universal package manager for GitHub releases.

Example: ei ilai-deutel/kibi automatically downloads the right binary for your platform, extracts it to ~/.ei, and adds it to your shell's PATH.

I wanted to run this on OpenWrt routers, which typically have only ~30MB of available storage. Even with standard release optimizations, the binary was still ~10MB:

[profile.release]
debug = false
lto = true
strip = true
opt-level = 3
codegen-units = 1
panic = "abort"
Enter fullscreen mode Exit fullscreen mode

Not good enough.

The Analysis

I used bloaty-metafile to analyze where the bloat was coming from. Turns out, ~80% of the binary size came from just 20% of dependencies:

  • clap - CLI argument parsing
  • reqwest - HTTP downloads
  • tokio - Async runtime
  • regex - Parsing non-standard release filenames (e.g., biome-linux-x64-muslx86_64-unknown-linux-musl)
  • easy-archive - Archive extraction (tar.gz, zip, etc.)

The Optimization

The key insight: disable default features and only enable what you actually use.

1. clap - Saved 100-200KB

clap = { version = "4", features = ["derive", "std"], default-features = false }
Enter fullscreen mode Exit fullscreen mode

Only enable basic functionality. No color output, no suggestions, no fancy formatting.

2. reqwest - Saved ~4MB (!!)

reqwest = { version = "0.12", features = [
  "json",
  "rustls-tls",  # Instead of native-tls
  "gzip"
], default-features = false }
Enter fullscreen mode Exit fullscreen mode

Switching from native-tls to rustls-tls was the biggest win. Native TLS pulls in system dependencies that bloat the binary significantly.

3. tokio - Saved ~100KB

tokio = { version = "1", features = [
  "macros",
  "rt-multi-thread",
], default-features = false }
Enter fullscreen mode Exit fullscreen mode

Only enable the multi-threaded runtime and macros. No I/O, no time, no sync primitives we don't use.

4. regex - Saved ~1MB

regex = { version = "1", default-features = false, features = ["std"] }
Enter fullscreen mode Exit fullscreen mode

Since we only use regex occasionally for URL parsing, we can disable Unicode support and other features.

5. easy-archive - Saved ~1MB

Only enable tar.gz decoding, skip encoding and other formats we don't need.

Results

Before: 11MB
After: 4.5MB

Most crates enable way more than you need. The 80/20 rule applies here - optimizing a few key dependencies can yield massive savings.


Links:

Top comments (0)