From 4e740c789b64b78c792a52738e86a2ac1ae70e57 Mon Sep 17 00:00:00 2001 From: TimJentzsch Date: Sun, 29 Dec 2024 18:26:07 +0100 Subject: [PATCH] Optimize binary with `wasm-opt` in release mode (#206) # Objective Closes #196, unblocks . With `wasm-opt`, we can further increase the performance and reduce the file size of the Wasm binary we use for web builds. This speeds ups the app both in-game and the loading times. # Solution As a simple first solution, we add a hard-coded size optimization pass in release mode. In future PRs, we can make this more configurable. To the user, we log the time the optimization took as well as the file size reduction as percentage. This is behind the `wasm-opt` feature flag (currently disabled by default), to give the user a way to turn this off and because this increases compile times of the CLI quite a bit. --- Cargo.lock | 238 ++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 8 ++ src/build/args.rs | 6 ++ src/build/mod.rs | 5 + src/lib.rs | 1 + src/run/args.rs | 6 ++ src/run/mod.rs | 5 + src/web/mod.rs | 2 + src/web/wasm_opt.rs | 38 +++++++ 9 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 src/web/mod.rs create mode 100644 src/web/wasm_opt.rs diff --git a/Cargo.lock b/Cargo.lock index a933dfa..6f040a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -211,7 +211,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -497,6 +497,7 @@ dependencies = [ "semver", "serde", "serde_json", + "wasm-opt", "webbrowser", ] @@ -522,7 +523,7 @@ checksum = "3fbfc33a4c6b80760bb8bf850a2cc65a1e031da62fd3ca8b552189104dc98514" dependencies = [ "bevy_macro_utils", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -569,7 +570,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -662,7 +663,7 @@ checksum = "bfc65e570012e64a21f3546df68591aaede8349e6174fb500071677f54f06630" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "toml_edit", ] @@ -713,7 +714,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "uuid", ] @@ -779,7 +780,7 @@ checksum = "38f1ab8f2f6f58439d260081d89a42b02690e5fdd64f814edc9417d33fcf2857" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -912,7 +913,7 @@ dependencies = [ "fs-err", "git2", "gix-config", - "heck", + "heck 0.5.0", "home", "ignore", "indexmap", @@ -1019,10 +1020,10 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1043,6 +1044,16 @@ dependencies = [ "serde", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + [[package]] name = "color-eyre" version = "0.6.3" @@ -1280,6 +1291,65 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cxx" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" +dependencies = [ + "cc", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" +dependencies = [ + "cc", + "codespan-reporting", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.89", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.89", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1299,7 +1369,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.89", ] [[package]] @@ -1354,7 +1424,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1489,6 +1559,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1945,6 +2021,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2222,7 +2304,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2492,6 +2574,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2537,7 +2628,7 @@ checksum = "3b51f1d220e3fa869e24cfd75915efe3164bd09bb11b3165db3f37f57bf673e3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2827,7 +2918,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2965,7 +3056,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3251,7 +3342,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3358,6 +3449,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" version = "1.0.18" @@ -3398,6 +3495,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "security-framework" version = "2.11.1" @@ -3447,7 +3550,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3620,12 +3723,42 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -3654,7 +3787,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3690,6 +3823,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal-prompt" version = "0.2.3" @@ -3742,7 +3884,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3753,7 +3895,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3943,7 +4085,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -4216,7 +4358,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -4250,7 +4392,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4261,6 +4403,46 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasm-opt" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" +dependencies = [ + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror 1.0.69", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", +] + [[package]] name = "web-sys" version = "0.3.72" @@ -4615,7 +4797,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -4637,7 +4819,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -4657,7 +4839,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -4686,7 +4868,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 36ae76d..8d56951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,11 @@ default-run = "bevy" name = "bevy" path = "src/bin/main.rs" +[features] +# To optimize the Wasm binaries +# Increases compile times (of the CLI) quite a bit +wasm-opt = ["dep:wasm-opt"] + [dependencies] # CLI argument parsing clap = { version = "4.5.16", features = ["derive"] } @@ -42,3 +47,6 @@ actix-web = "4.9.0" # Opening the app in the browser webbrowser = "1.0.2" + +# Optimizing Wasm binaries +wasm-opt = { version = "0.116.1", optional = true } diff --git a/src/build/args.rs b/src/build/args.rs index 06efcad..fe463ef 100644 --- a/src/build/args.rs +++ b/src/build/args.rs @@ -19,6 +19,12 @@ impl BuildArgs { matches!(self.subcommand, Some(BuildSubcommands::Web)) } + /// Whether to build with optimizations. + #[cfg(feature = "wasm-opt")] + pub(crate) fn is_release(&self) -> bool { + self.cargo_args.compilation_args.is_release + } + /// The profile used to compile the app. pub(crate) fn profile(&self) -> &str { self.cargo_args.compilation_args.profile() diff --git a/src/build/mod.rs b/src/build/mod.rs index 55f4ceb..d49f8d0 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -28,6 +28,11 @@ pub fn build(args: &BuildArgs) -> anyhow::Result<()> { args.profile(), )?; wasm_bindgen::bundle(&bin_target)?; + + #[cfg(feature = "wasm-opt")] + if args.is_release() { + crate::web::wasm_opt::optimize_bin(&bin_target)?; + } } else { cargo::build::command().args(cargo_args).ensure_status()?; } diff --git a/src/lib.rs b/src/lib.rs index c56abc8..f7eab7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod external_cli; pub mod lint; pub mod run; pub mod template; +pub(crate) mod web; diff --git a/src/run/args.rs b/src/run/args.rs index b358f81..26b4d67 100644 --- a/src/run/args.rs +++ b/src/run/args.rs @@ -19,6 +19,12 @@ impl RunArgs { matches!(self.subcommand, Some(RunSubcommands::Web(_))) } + /// Whether to build with optimizations. + #[cfg(feature = "wasm-opt")] + pub(crate) fn is_release(&self) -> bool { + self.cargo_args.compilation_args.is_release + } + /// The profile used to compile the app. pub(crate) fn profile(&self) -> &str { self.cargo_args.compilation_args.profile() diff --git a/src/run/mod.rs b/src/run/mod.rs index a202ef2..d5f57eb 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -38,6 +38,11 @@ pub fn run(args: &RunArgs) -> anyhow::Result<()> { )?; wasm_bindgen::bundle(&bin_target)?; + #[cfg(feature = "wasm-opt")] + if args.is_release() { + crate::web::wasm_opt::optimize_bin(&bin_target)?; + } + let port = web_args.port; let url = format!("http://localhost:{port}"); diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 0000000..0566276 --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "wasm-opt")] +pub(crate) mod wasm_opt; diff --git a/src/web/wasm_opt.rs b/src/web/wasm_opt.rs new file mode 100644 index 0000000..ce05c8c --- /dev/null +++ b/src/web/wasm_opt.rs @@ -0,0 +1,38 @@ +use std::{fs, path::Path, time::Instant}; + +use anyhow::Context as _; + +use crate::run::BinTarget; + +/// Optimize the binary with wasm-opt. +pub(crate) fn optimize_bin(bin_target: &BinTarget) -> anyhow::Result<()> { + let wasm_path = bin_target + .artifact_directory + .clone() + .join(format!("{}_bg.wasm", bin_target.bin_name)); + + optimize_path(&wasm_path) +} + +/// Optimize the Wasm binary at the given path with wasm-opt. +fn optimize_path(path: &Path) -> anyhow::Result<()> { + println!("Optimizing with wasm-opt..."); + + let start = Instant::now(); + let size_before = fs::metadata(path)?.len(); + + wasm_opt::OptimizationOptions::new_optimize_for_size() + .run(path, path) + .context("failed to optimize with wasm-opt")?; + + let size_after = fs::metadata(path)?.len(); + let size_reduction = 1. - (size_after as f32) / (size_before as f32); + let duration = start.elapsed(); + + println!( + " Finished in {duration:.2?}. Size reduced by {:.0}%.", + size_reduction * 100. + ); + + Ok(()) +}