Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strip packaged binaries to reduce buildpack size #319

Closed
edmorley opened this issue Feb 13, 2022 · 4 comments · Fixed by #445
Closed

Strip packaged binaries to reduce buildpack size #319

edmorley opened this issue Feb 13, 2022 · 4 comments · Fixed by #445
Assignees
Labels
faster tests Things that improve the runtime of tests libcnb-package slim buildpacks Things that affect the size of the compiled binaries or packaged buildpacks

Comments

@edmorley
Copy link
Member

edmorley commented Feb 13, 2022

Stripping the packaged binaries will reduce the buildpack size, which will help with builder image pull times, and also local development/integration test runtimes (the copy of local buildpacks into the ephemeral builder image when using --buildpack is surprisingly slow).

Rust 1.58 stabilised the -C strip option:
https://doc.rust-lang.org/stable/rustc/codegen-options/index.html#strip

However it's not until Rust 1.59 (currently in beta, due for release 2022-02-24) that corresponding support will be added to Cargo:
https://doc.rust-lang.org/beta/cargo/reference/profiles.html#strip

The feature can be enabled by adding this to Cargo.toml for the crate in question:

[profile.release]
strip = true

However this would mean every project using libcnb would need to remember to do this, so I think it would make more sense if we configure it automatically in libcnb-cargo.

I think we may want to enable it for non-release libcnb-cargo builds too, since it will speed up development use-cases as mentioned above. (And debug symbols don't exactly help much when the buildpack is run inside the container as part of the lifecycle, since no easy way to run via a debugger anyway.)

Profile options like this can't be passed via cargo build arguments:
https://doc.rust-lang.org/beta/cargo/commands/cargo-build.html

...but we should be able to enable it via env var:
https://doc.rust-lang.org/beta/cargo/reference/config.html#environment-variables

For example:

CARGO_PROFILE_RELEASE_STRIP=true
CARGO_PROFILE_DEV_STRIP=true

GUS-W-11324847.

@edmorley
Copy link
Member Author

edmorley commented Feb 14, 2022

Experimenting with this using Rust 1.59 beta, by running CARGO_PROFILE_RELEASE_STRIP=true cargo libcnb package --release, I get the following size reductions:

  • example-01-basics: 12.0 MB -> 4.1 MB
  • example-02-ruby-sample: 20.5 MB -> 8.5 MB

And for non-release builds (using CARGO_PROFILE_DEV_STRIP=true cargo libcnb package), I get:

  • example-01-basics: 50.4 MB -> 10.4 MB
  • example-02-ruby: 97.6 MB -> 20.7 MB

During the build, this warning appears:

   Compiling libcnb-proc-macros v0.1.1 (/Users/emorley/src/libcnb.rs/libcnb-proc-macros)
warning: stripping debug info with `strip` failed: exit status: 1
  |
  = note: /Library/Developer/CommandLineTools/usr/bin/strip: error: symbols referenced by indirect symbol table entries that can't be stripped in: /Users/emorley/src/libcnb.rs/target/release/deps/liblibcnb_proc_macros-f34f391f2c4d9c8e.dylib
          __NSGetArgc
          __NSGetArgv
          __NSGetEnviron
          __NSGetExecutablePath
          __Unwind_Backtrace
          __Unwind_DeleteException
          __Unwind_GetCFA
          __Unwind_GetDataRelBase
          __Unwind_GetIP
          __Unwind_GetIPInfo
          __Unwind_GetLanguageSpecificData
          __Unwind_GetRegionStart
          __Unwind_GetTextRelBase
          __Unwind_RaiseException
          __Unwind_Resume
          __Unwind_SetGR
          __Unwind_SetIP
...

The build completes successfully however.

What's interesting about this, is that the path referenced in the error message is under target/release/ not target/x86_64-unknown-linux-musl/release/. This plus the fact it's for the libcnb-proc-macros crate, makes me think it's the stripping of the compile-time regex validation that's failing (which is compiled for the host), rather than the cross-compiled crate itself.

Trying again using a strip mode of debuginfo rather than true (true being equivalent to symbols, the maximum level), the warning goes away, and I get the following (slightly larger) output sizes:

  • example-01-basics: 5.4 MB (release), 14.5 MB (dev)
  • example-02-ruby: 11.3 MB (release), 28.9 MB (dev)

The Cargo docs mention a build-override setting:
https://doc.rust-lang.org/beta/cargo/reference/config.html#profilenamebuild-override

The build-override table overrides settings for build scripts, proc macros, and their dependencies. It has the same keys as a normal profile.

We can take advantage of that by using:
CARGO_PROFILE_DEV_STRIP=true CARGO_PROFILE_DEV_BUILD_OVERRIDE=none cargo libcnb package

...which prevents the warning, whilst still giving us the full output size reductions.

It seems like perhaps this might be a Rust bug where it's not fully taking into account the strip options required for the host vs the target, but either way, there is an easy workaround for now :-)

@edmorley
Copy link
Member Author

It seems like perhaps this might be a Rust bug

Filed:
rust-lang/rust#93988

@edmorley
Copy link
Member Author

@edmorley edmorley added faster tests Things that improve the runtime of tests slim buildpacks Things that affect the size of the compiled binaries or packaged buildpacks labels Mar 1, 2022
@edmorley
Copy link
Member Author

Filed:
rust-lang/rust#93988

The warnings seen when stripping using local path dependencies (ie when hacking on libcnb in this repo) will now be resolved in Rust 1.63, thanks to:
rust-lang/rust#97991

That needn't block the work on enabling stripping in libcnb, since (a) they are only warnings, (b) they aren't shown when people use libcnb from crates.io (only local path based builds).

@edmorley edmorley self-assigned this Jul 4, 2022
edmorley added a commit that referenced this issue Jul 4, 2022
As of Rust 1.59, Cargo now supports a `strip` option, which when enabled,
causes the binaries output to stripped of all symbol information:
https://blog.rust-lang.org/2022/02/24/Rust-1.59.0.html#creating-stripped-binaries
https://doc.rust-lang.org/beta/cargo/reference/profiles.html#strip

Doing this significantly reduces the size of the binaries, which:
- Results in smaller overall production builder image sizes, which
  reduces push/pull times.
- Helps speed up workflows where the otherwise large dev/debug builds
  were being used (such as integration tests or local buildpack development),
  since Docker/`pack build`'s transfer of files into the ephemeral builder image
  is very slow.
- Helps reduce any end-user perception of `libcnb.rs` powered CNBs being
  bloated compared to say bash-based CNBs (and also keeps us competitive
  with any Go-based CNB frameworks).

Before/after comparison:
- `examples/basics`:
  - dev build: 22.01 MiB -> 3.26 MiB
  - release build: 5.04 MiB -> 1.29 MiB
- `examples/exec.d`:
  - dev build: 37.40 MiB -> 4.10 MiB
  - release build: 9.02 MiB -> 1.78 MiB
  - `cargo test -- --ignored`: 5.5s -> 2.6s
- `heroku/procfile`:
  - dev build: 43.40 MiB -> 5.16 MiB
  - release build: 6.27 MiB -> 2.17 MiB
  - `cargo test -- --ignored`: 9.1s -> 5.2s
- `heroku/jvm`:
  - dev build: 116.55 MiB -> 15.52 MiB
  - release build: 24.40 MiB -> 7.38 MiB
  - `cargo test --test integration_tests`: 97s -> 82s

Stripping the binaries does not result in any loss of useful backtraces, since
with the cross-compilation and/or usage of MUSL, these did not work even
in debug buildpack builds anyway. Both before and after this change, any
panics still show the originating file/line number, which is all that's generally
required when debugging. Lastly, even if backtraces had previously worked,
they are a pain to enable given that the env has to be set outside of the
buildpack invocation, so can't be used when `clear_env = true` is set etc.

Since Cargo only supports the `strip` option as of Rust 1.59, the minimum
Rust version has been bumped to match.

Fixes #319.
GUS-W-11324847.
edmorley added a commit that referenced this issue Jul 5, 2022
As of Rust 1.59, Cargo now supports a `strip` option, which when enabled,
causes the binaries output to stripped of all symbol information:
https://blog.rust-lang.org/2022/02/24/Rust-1.59.0.html#creating-stripped-binaries
https://doc.rust-lang.org/beta/cargo/reference/profiles.html#strip

Doing this significantly reduces the size of the binaries, which:
- Results in smaller overall production builder image sizes, which
  reduces push/pull times.
- Helps speed up workflows where the otherwise large dev/debug builds
  were being used (such as integration tests or local buildpack development),
  since Docker/`pack build`'s transfer of files into the ephemeral builder image
  is very slow.
- Helps reduce any end-user perception of `libcnb.rs` powered CNBs being
  bloated compared to say bash-based CNBs (and also keeps us competitive
  with any Go-based CNB frameworks).

Before/after comparison:
- `examples/basics`:
  - dev build: 22.01 MiB -> 3.26 MiB
  - release build: 5.04 MiB -> 1.29 MiB
- `examples/exec.d`:
  - dev build: 37.40 MiB -> 4.10 MiB
  - release build: 9.02 MiB -> 1.78 MiB
  - `cargo test -- --ignored`: 5.5s -> 2.6s
- `heroku/procfile`:
  - dev build: 43.40 MiB -> 5.16 MiB
  - release build: 6.27 MiB -> 2.17 MiB
  - `cargo test -- --ignored`: 9.1s -> 5.2s
- `heroku/jvm`:
  - dev build: 116.55 MiB -> 15.52 MiB
  - release build: 24.40 MiB -> 7.38 MiB
  - `cargo test --test integration_tests`: 97s -> 82s

Stripping the binaries does not result in any loss of useful backtraces, since
with the cross-compilation and/or usage of MUSL, these did not work even
in debug buildpack builds anyway. Both before and after this change, any
panics still show the originating file/line number, which is all that's generally
required when debugging. Lastly, even if backtraces had previously worked,
they are a pain to enable given that the env has to be set outside of the
buildpack invocation, so can't be used when `clear_env = true` is set etc.

Since Cargo only supports the `strip` option as of Rust 1.59, the minimum
Rust version has been bumped to match.

Fixes #319.
GUS-W-11324847.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
faster tests Things that improve the runtime of tests libcnb-package slim buildpacks Things that affect the size of the compiled binaries or packaged buildpacks
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant