From 69167a7a4fb6c75679b735891a5cd085fb179509 Mon Sep 17 00:00:00 2001 From: keiko233 Date: Wed, 21 Oct 2026 09:27:01 +0800 Subject: [PATCH] perf: reduce release executable size --- .github/workflows/release.yml | 3 +- Cargo.toml | 8 +++++ README.md | 17 +++++++++++ docs/release-size.md | 56 +++++++++++++++++++++++++++++++++++ rust-toolchain.toml | 2 +- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 docs/release-size.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74c90d0..dae9b0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,13 +23,14 @@ jobs: submodules: true - uses: dtolnay/rust-toolchain@nightly with: + components: rust-src targets: ${{ matrix.target }} - name: Check the tag matches the crate version shell: bash run: | version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "enable-loopback") | .version') test "v$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME does not match crate version $version"; exit 1; } - - run: cargo build --release --locked --target ${{ matrix.target }} + - run: cargo build --release --locked --target ${{ matrix.target }} -Z build-std=std,panic_abort -Z build-std-features=optimize_for_size - name: Name the asset and its checksum shell: bash run: | diff --git a/Cargo.toml b/Cargo.toml index 8ecf049..18f66cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,11 @@ repository = "https://github.com/libnyanpasu/enable-loopback.git" [profile.dev] debug = "line-tables-only" + +[profile.release] +opt-level = "z" +lto = "fat" +codegen-units = 1 +# Native GUI callbacks cannot unwind across the Windows ABI boundary. +panic = "abort" +strip = "symbols" diff --git a/README.md b/README.md index 230e2d1..94a3fea 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,23 @@ cargo run -- --lang zh-cn If a global Cargo configuration uses a compiler wrapper, set `RUSTC_WRAPPER=` to bypass it for these commands. Do not change the global configuration. +For a size-optimized release, use the same command as the release workflow: + +```sh +cargo build --release --locked --target x86_64-pc-windows-msvc \ + -Z build-std=std,panic_abort -Z build-std-features=optimize_for_size +``` + +Use `aarch64-pc-windows-msvc` for arm64. This requires nightly and `rust-src` +(included in `rust-toolchain.toml`). The executable is in +`target//release/enableLoopback.exe`. A plain `cargo build --release` +also uses the size profile, but keeps the precompiled standard library and is +slightly larger. Release builds use full LTO, one codegen unit, size optimization, +stripped symbols, and abort on panic; development builds retain unwinding and +line tables. Recoverable application errors still use the existing error dialogs. + +See [release size measurements](docs/release-size.md) for the measured tradeoffs. + `enableLoopback.exe [--lang CODE]` opens the GUI. Supported languages: `en`, `zh-cn`, `zh-tw`, `ja`, `ko`, `ru`; otherwise the system language is used, with English as the fallback. `manual` skips automatic refresh and `-registry` diff --git a/docs/release-size.md b/docs/release-size.md new file mode 100644 index 0000000..043513f --- /dev/null +++ b/docs/release-size.md @@ -0,0 +1,56 @@ +# Release executable size + +Measured on 2026-10-21 with the locked dependencies, windows-rs submodule +`75c2364d1ba98d4b1fb9a9f28e2a9b0fc067e6e6`, and +`rustc 1.100.0-nightly (a36d05efa 2026-09-09)`, targeting Windows x64 MSVC. +Sizes are the actual uncompressed EXE, including its icon and manifest. + +| Configuration | Bytes | MiB | Reduction from baseline | +| --- | ---: | ---: | ---: | +| Original default release profile | 3,802,624 | 3.63 | — | +| Full LTO, one codegen unit, `s`, abort, strip | 1,773,056 | 1.69 | 53.4% | +| Full LTO, one codegen unit, `z`, abort, strip | 1,442,304 | 1.38 | 62.1% | +| Same `z` profile, size-optimized standard library | 1,302,016 | 1.24 | 65.8% | + +`z` saves 330,752 bytes compared with `s` in this application. Rebuilding the +standard library saves another 140,288 bytes. These measurements select the +release profile and the release workflow's `build-std` options; they do not +establish an absolute lower bound. Compiler updates and architectures can change +the result. + +The original PE contained 2,680,320 bytes of code, 894,976 bytes of read-only data, +and only 55,808 bytes of icon/manifest resources. Optimizing generated code +therefore provided much more benefit than changing the artwork. The implementation +keeps all six languages, the GUI, CLI, and existing runtime deployment model. +No executable packer is used. + +Full LTO trades build time for size. Size optimization may trade throughput for +size; interactive behavior was checked, but this is not a performance benchmark. +`panic = "abort"` terminates on programming panics instead of unwinding; it does +not change ordinary `Result` error handling. Native GUI callbacks already cannot +unwind across the Windows ABI. Tests retain the test harness's unwind behavior. +Symbol stripping reduces postmortem debugging information. The additional +standard-library optimization requires nightly's unstable `build-std` support +and `rust-src`; the normal release command remains available without it. + +Validation: + +- All 52 application tests passed in both development and release profiles. +- `cargo clippy --all-targets --locked -- -D warnings` and application formatting + checks passed. +- ARM64 compilation reached the final link, but this machine has only x64/x86 + MSVC build tools and no ARM64 linker. Its EXE size and runtime behavior remain + unverified locally; the release workflow retains both architecture targets. +- The smallest x64 EXE loaded all 136 real AppContainers. Native GUI checks + exercised checkbox edits and counter updates, refresh cancellation and refresh, + dirty-close confirmation and cancellation, and clean close. Firewall exemptions + were compared before/after and were unchanged; Save was never invoked. +- The repository's existing GUI smoke script has stale sort-header expectations + and requests a SelectionItemPattern unavailable on this runtime's rows; the + original baseline fails those checks too. A temporary adapter under + `target/size-study` used the current header names, omitted native selection/sort + assertions, and waited for dialog dismissal animations. Selection and sorting + are covered by the application tests, but were not verified through native UIA. + +Reproduce the smallest build with the command in the README. Intermediate x64 +EXEs from this investigation are retained locally in `target/size-study/`. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8e275b7..151a1a3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly" -components = ["rustfmt", "clippy"] +components = ["rustfmt", "clippy", "rust-src"]