Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## wasm-split-cli vfuture

- Add `Options::cost_model` (`--request-cost` and `--load-probability` in the cli). Shared chunks
are merged when that lowers the expected loading cost, which weighs the requests saved against
the extra bytes loaded, and counts data as well as code. With many split points this cuts the
number of chunks one split module loads from hundreds to tens. The web target merges by default,
the bundler target does not. `--request-cost 0` keeps the previous output.
- Add `Options::cost_model` with a bernoulli model (`--cost-model` in the cli). Shared chunks
are merged when that lowers the expected loading time, which weighs the requests saved against
the extra bytes loaded. This tends to cut the number of chunks one split module loads from
hundreds to tens. The default cost model depends on the target. The web target uses the
new cost model, the bundler target keeps the previous output, equivalent to `--cost-model=none`.

## wasm-split-cli v0.2.4

Expand Down
20 changes: 11 additions & 9 deletions crates/wasm_split_cli/src/bin/wasm-split-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ enum CostModel {
#[default]
Auto,
None,
Expected,
Binary,
}

#[derive(Debug, Args)]
#[command(next_help_heading = "Options for `--cost-model=expected`")]
struct CostModelExpectedOpts {
#[command(next_help_heading = "Options for `--cost-model=binary`")]
struct CostModelBinaryOpts {
/// What one more chunk request costs, in downloaded bytes. 0 keeps every chunk as
/// split.
#[arg(long, default_value_t = 16 * 1024)]
Expand Down Expand Up @@ -59,7 +59,7 @@ struct Cli {
verbose: bool,

#[clap(flatten)]
cost_model_exp_opts: CostModelExpectedOpts,
cost_model_bin_opts: CostModelBinaryOpts,
}

fn main() -> Result<()> {
Expand All @@ -77,13 +77,15 @@ fn main() -> Result<()> {
let mut opts = this::Options::new(&input_wasm);
opts.verbose = args.verbose;
match args.cost_model.unwrap_or_default() {
CostModel::Expected if args.cost_model_exp_opts.request_cost > 0 => {
opts.cost_model = Some(this::CostModel::Expected(this::BernoulliCost {
request_cost: args.cost_model_exp_opts.request_cost,
load_probability: args.cost_model_exp_opts.load_probability,
CostModel::Binary if args.cost_model_bin_opts.request_cost > 0 => {
opts.cost_model = Some(this::CostModel::Binary(this::BernoulliCost {
request_cost: args.cost_model_bin_opts.request_cost,
load_probability: args.cost_model_bin_opts.load_probability,
}))
}
CostModel::Expected | CostModel::None => opts.cost_model = Some(this::CostModel::None),
CostModel::Binary | CostModel::None => {
opts.cost_model = Some(this::CostModel::DisableMerging)
}
_ => {}
}
opts.output_dir = &args.output;
Expand Down
6 changes: 3 additions & 3 deletions crates/wasm_split_cli/src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct Group {
members: Vec<SplitModuleIdentifier>,
}

/// [`CostModel::Expected`] with both parameters as `f64`.
/// [`CostModel::Binary`] with both parameters as `f64`.
struct ExpectedCost {
request_cost: f64,
load_probability: f64,
Expand Down Expand Up @@ -144,8 +144,8 @@ pub fn merge_chunks(
request_cost,
load_probability,
} = match cost_model {
CostModel::None => return,
CostModel::Expected(bernoulli_cost) => *bernoulli_cost,
CostModel::DisableMerging => return,
CostModel::Binary(bernoulli_cost) => *bernoulli_cost,
};
let perf_span = perf_span!("merge chunks");
let _perf_span = perf_span.enter();
Expand Down
31 changes: 17 additions & 14 deletions crates/wasm_split_cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,35 @@ pub struct BernoulliCost {
#[derive(Debug)]
#[non_exhaustive]
pub enum CostModel {
/// Keep every chunk as split.
None,
/// Merge chunks when that lowers the expected loading cost.
Expected(BernoulliCost),
/// Keep every chunk as-is, disables chunk merging. This is equivalent to assuming
/// that a chunk load has no cost.
DisableMerging,
/// Merge chunks when that lowers the expected loading cost. Each split module's
/// loading state is modelled as a [binary random variable].
///
/// [binary random variable]: https://en.wikipedia.org/wiki/Bernoulli_distribution
Binary(BernoulliCost),
}

impl OutputTarget {
/// Chunks of the web target are fetched over the network, one request each.
/// A bundler decides on its own how they get loaded.
fn default_cost_model(&self) -> CostModel {
// Chunks of the web target are fetched over the network, so we use the bernoulli loading model here.
// A bundler decides on its own how chunks get loaded.
match self {
Self::Web(_) => CostModel::Expected(BernoulliCost {
request_cost: 16384,
Self::Web(_) => CostModel::Binary(BernoulliCost {
request_cost: 16 * 1024,
load_probability: 0.05,
}),
Self::Bundler(_) => CostModel::None,
Self::Bundler(_) => CostModel::DisableMerging,
}
}
}

impl CostModel {
pub(crate) fn validate(&self) -> eyre::Result<()> {
match self {
Self::None => {}
Self::Expected(model) => {
Self::DisableMerging => {}
Self::Binary(model) => {
eyre::ensure!(
0. <= model.load_probability && model.load_probability <= 1.,
"expected a load probability between 0 and 1, got {}",
Expand Down Expand Up @@ -147,11 +151,10 @@ pub struct Options<'a> {
/// This option is experimental.
/// Default: `true` if the `WASM_SPLIT_CLI_ENABLE_DWARF` environment variable is non-empty.
pub emit_dwarf: bool,
/// How to merge the shared chunks.
///
/// The default model is not subject to semver stability guarantees.
/// A cost model under which chunk optimization is performed.
///
/// Default: `None`, which picks a default model based on the output target.
/// This default model is not subject to semver stability guarantees.
pub cost_model: Option<CostModel>,
/// Enables explicit tests for assumptions we make about the input wasm file during integration testing.
#[doc(hidden)]
Expand Down
2 changes: 1 addition & 1 deletion crates/wasm_split_cli/tests/no_reloc_stub_end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ fn no_reloc_stub_is_rewritten_and_dangerous_stub_is_raw_copied() {
opts.output_dir = tmp.path();
opts.main_out_path = &main_out;
// These tests assert where the painter puts symbols, before any merging.
opts.cost_model = Some(CostModel::None);
opts.cost_model = Some(CostModel::DisableMerging);

transform(opts).expect("transform succeeds");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ fn split_wasm(wasm: &[u8]) -> Output {
opts.output_dir = tmp.path();
opts.main_out_path = &main_out;
// These tests assert where the painter puts symbols, before any merging.
opts.cost_model = Some(CostModel::None);
opts.cost_model = Some(CostModel::DisableMerging);

let link_path = tmp.path().join(opts.link_name);
let output = transform(opts).expect("transform succeeds");
Expand Down
2 changes: 1 addition & 1 deletion integration/simple/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ wasm-bindgen-test.workspace = true
tokio.workspace = true

[package.metadata.wasm-split-test-runner]
cost-model = "none"
cost-model = "disable-merging"
17 changes: 11 additions & 6 deletions test-runner/src/bin/wasm-split-test-runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,10 @@ fn with_perf_tracing<R>(report_dir: &Path, f: impl FnOnce() -> R) -> R {
}

#[derive(serde::Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
#[serde(rename_all = "kebab-case")]
enum CostModel {
Auto,
None,
DisableMerging,
}

#[derive(serde::Deserialize, Debug)]
Expand Down Expand Up @@ -268,10 +268,15 @@ fn wasm_split_cli(
let split = with_perf_tracing(report_dir, || {
wasm_split_cli_support::transform({
let mut split_opts = wasm_split_cli_support::Options::new(&input);
if let Some(CostModel::None) =
options.as_ref().and_then(|opts| opts.cost_model.as_ref())
{
split_opts.cost_model = Some(wasm_split_cli_support::CostModel::None);
let cost_model = options
.as_ref()
.and_then(|opts| opts.cost_model.as_ref())
.unwrap_or(&CostModel::Auto);
match cost_model {
CostModel::DisableMerging => {
split_opts.cost_model = Some(wasm_split_cli_support::CostModel::DisableMerging)
}
CostModel::Auto => {}
}
split_opts.output_dir = dir;
split_opts.main_out_path = &main_file;
Expand Down
Loading