TeXitoi / structopt

Parse command line arguments by defining a struct.
Other
2.71k stars 151 forks source link

Size issues (again?) #489

Closed RandomInsano closed 3 years ago

RandomInsano commented 3 years ago

I've been playing around with getting my final binaries smaller and after finding #46 I'm somewhat confused about why structopts is the biggest contributor to my binary size after the standard library. What's a little funny is turning default features off grows the final binary so there's nowhere to go but up? This is all on Linux with 1.56.0-nightly but I see the same results in stable.

Here's some experiments:

-rwxrwxr-x 2 me me 3.5M Aug 21 01:27 target/release/test_opts (without structopt code)
-rwxrwxr-x 2 me me 4.1M Aug 21 01:29 target/release/test_opts
-rwxrwxr-x 2 me me 4.2M Aug 21 01:26 target/release/test_opts (after default-features = false)

The code for this:

use structopt::StructOpt;
use std::path::PathBuf;

#[derive(StructOpt, Debug)]
enum ProgramOptions {
    New {
        #[structopt(parse(from_os_str), help = "File path for new level")]
        filename: PathBuf,

        #[structopt(short, long, help = "Level name")]
        name: String,

        #[structopt(short, long, help = "Width for new level")]
        width: usize,

        #[structopt(short, long, help = "Height for new level")]
        height: usize,
    },
    Load {
        #[structopt(parse(from_os_str), help = "File path for new level")]
        filename: PathBuf,
    }
}

pub fn main() {
    // Comment this line out to compare and contrast sizes
    parse_args().unwrap();
}

pub fn parse_args() -> Result<(), &'static str> {
    let opts = ProgramOptions::from_args();

    match opts {
        ProgramOptions::New { ref filename, ref name, width, height } => {
            let filename = match filename.to_str() {
                Some(y) => y,
                None => return Err("Unable to parse filename into OS string".into()),
            };

            println!("Use some vars: {:?}, {}, {}, {}", filename, name, width, height);
        },
        ProgramOptions::Load { ref filename } => {
            let filename = match filename.to_str() {
                Some(y) => y,
                None => return Err("Unable to parse filename into OS string".into()),
            };

            println!("Use some vars: {:?}", filename);
        }
    }

    Ok(())
}
RandomInsano commented 3 years ago

I built a simple script to test sizes over time. Nothing terribly exciting. Without specifying options, the size changes very little. Output below shows only size changes and removed any failed builds. Tested between v0.1.4 and v0.3.22 which was output from git tag

Build 0.3.5 size is 4196 target/release/test_opts
Build 0.3.22 size is    4200 target/release/test_opts
...didn't include latest from git tags somehow?!

Disabling default features has about the same effect

Build 0.3.5 size is 4220 target/release/test_opts
Build 0.3.22 size is    4224 target/release/test_opts
#!/usr/bin/env bash
BIN_NAME=test_opts

function build() {
        cp Cargo.template Cargo.toml
        rm -f Cargo.lock
        echo "structopt = {version =\"=$1\", default-features=false}" >> Cargo.toml

        echo -n "Build $1 size is       "
        if `cargo build --release -q 2>/dev/null`; then
                ls -s target/release/$BIN_NAME
        else
                echo "unknown (build failed)"
        fi
}

for version in `cat versions | sort -V`; do
        build $version
done
TeXitoi commented 3 years ago

Strange, no idea after a quick look at the code, the features seems well forwarded to clap. Do you see the same behavior if using only clap?

TeXitoi commented 3 years ago

cargo bloat with default-features:

 File  .text     Size Crate
 7.8%  54.6% 297.7KiB clap
 6.0%  41.9% 228.3KiB std
 0.1%   0.5%   3.0KiB strsim
 0.1%   0.5%   3.0KiB ansi_term
 0.0%   0.3%   1.7KiB textwrap
 0.0%   0.2%   1.1KiB test_rs
 0.0%   0.0%     213B [Unknown]
 0.0%   0.0%      28B atty
14.2% 100.0% 545.2KiB .text section size, the file size is 3.7MiB

with default-features = false:

 File  .text     Size Crate
 8.0%  55.8% 309.6KiB clap
 6.0%  41.7% 231.1KiB std
 0.0%   0.3%   1.7KiB textwrap
 0.0%   0.2%   1.1KiB test_rs
 0.0%   0.0%     213B [Unknown]
14.4% 100.0% 554.5KiB .text section size, the file size is 3.8MiB

Everything seems fine. The features don't really minimize the binary size.

RandomInsano commented 3 years ago

Default features being off results in a 0.1MiB increase in your test despite things like ansi_term not being included. I've moved to argh for now which isn't as featureful.

TeXitoi commented 3 years ago

Clap v2 is not really size optimized, so the code used in place of the external library is not a size gain. Nothing structopt can do.

You can also try clap master (future v3) than embeds directly structopt.

The clap features came at a size cost.

Sorry to see you leaving structopt, but there is alternatives because there is different needs :-)

RandomInsano commented 3 years ago

Absolutely. I only mention it so you have some feedback. Thanks!