From 45818f5ff15e9a5c69cdcdf19aa35a7fe7d6a72d Mon Sep 17 00:00:00 2001 From: Bart Waardenburg Date: Wed, 17 Jun 2026 14:49:46 +0200 Subject: [PATCH] chore: add codspeed benchmark workflow --- .github/workflows/bench.yml | 85 ++++ Cargo.lock | 419 +++++++++-------- Cargo.toml | 2 +- benchmarks/download-fixtures.mjs | 2 +- crates/codec/Cargo.toml | 2 +- crates/codec/benches/vlq.rs | 186 ++++---- crates/generator/Cargo.toml | 2 +- crates/generator/benches/generate.rs | 140 +++--- crates/ram-bundle/src/lib.rs | 4 +- crates/remapping/Cargo.toml | 2 +- crates/remapping/benches/remap.rs | 153 ++++--- crates/remapping/src/lib.rs | 3 +- crates/sourcemap/Cargo.toml | 2 +- crates/sourcemap/benches/parse.rs | 643 ++++++++++++++++----------- crates/sourcemap/src/lib.rs | 18 +- 15 files changed, 967 insertions(+), 696 deletions(-) create mode 100644 .github/workflows/bench.yml diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 0000000..395c4d6 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,85 @@ +name: Benchmarks + +on: + pull_request: + branches: [main] + paths: + - 'crates/**' + - 'benchmarks/download-fixtures.mjs' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/bench.yml' + push: + branches: [main] + paths: + - 'crates/**' + - 'benchmarks/download-fixtures.mjs' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/bench.yml' + workflow_dispatch: + +concurrency: + group: bench-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: {} + +env: + CARGO_TERM_COLOR: always + +jobs: + benchmark: + name: CodSpeed simulation (${{ matrix.label }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - label: codec vlq + cache_key: codec-vlq + package: srcmap-codec + bench: vlq + fixtures: false + - label: sourcemap parse + cache_key: sourcemap-parse + package: srcmap-sourcemap + bench: parse + fixtures: true + - label: generator + cache_key: generator + package: srcmap-generator + bench: generate + fixtures: false + - label: remapping + cache_key: remapping + package: srcmap-remapping + bench: remap + fixtures: false + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + key: bench-${{ matrix.cache_key }} + + - name: Download real-world fixtures + if: matrix.fixtures + run: node benchmarks/download-fixtures.mjs + + - name: Install cargo-codspeed + uses: taiki-e/install-action@15449e3094499af05d8d964a1c884208e4b8b595 # v2.81.11 + with: + tool: cargo-codspeed@4.7.0 + + - name: Build benchmark shard + run: cargo codspeed build -p ${{ matrix.package }} --bench ${{ matrix.bench }} + + - name: Run benchmark shard + uses: CodSpeedHQ/action@c145068895e045cc725ee76fcd2307624b65c3af # v4.17.5 + with: + mode: simulation + run: cargo codspeed run -p ${{ matrix.package }} --bench ${{ matrix.bench }} diff --git a/Cargo.lock b/Cargo.lock index 9aa0d95..b3d7c4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,27 +30,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloca" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" -dependencies = [ - "cc", -] - [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstream" version = "1.0.0" @@ -107,6 +92,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -137,12 +131,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.2.60" @@ -160,31 +148,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" @@ -206,6 +173,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -227,62 +195,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] -name = "colorchoice" -version = "1.0.5" +name = "codspeed" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +checksum = "57af92d1db7f6871b7e82c79cd87f2501db66f36b0eab924be6ea83dd6b2f3f3" +dependencies = [ + "anyhow", + "cc", + "colored", + "getrandom 0.2.17", + "glob", + "libc", + "nix", + "serde", + "serde_json", + "statrs", +] [[package]] -name = "convert_case" -version = "0.11.0" +name = "codspeed-divan-compat" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +checksum = "c4ea79fd0b1f2128cfac6308369013dba92df47baf4a4f66b57d8158224a361d" dependencies = [ - "unicode-segmentation", + "clap", + "codspeed", + "codspeed-divan-compat-macros", + "codspeed-divan-compat-walltime", + "regex", ] [[package]] -name = "crc32fast" -version = "1.5.0" +name = "codspeed-divan-compat-macros" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "f70e4ddd6beedefeb48f59d5f85fc21365a66e7976408c3d39f6cbbc4f03e08c" dependencies = [ - "cfg-if", + "divan-macros", + "itertools", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "criterion" -version = "0.8.2" +name = "codspeed-divan-compat-walltime" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +checksum = "490c04f6076be6eacfafb496b8b237f3efbbed93838f2689115cc6f35fcf81c9" dependencies = [ - "alloca", - "anes", - "cast", - "ciborium", + "cfg-if", "clap", - "criterion-plot", - "itertools", - "num-traits", - "oorandom", - "page_size", - "plotters", - "rayon", - "regex", - "serde", - "serde_json", - "tinytemplate", - "walkdir", + "codspeed", + "condtype", + "divan-macros", + "libc", + "regex-lite", ] [[package]] -name = "criterion-plot" -version = "0.8.2" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "cast", - "itertools", + "lazy_static", + "windows-sys 0.52.0", +] + +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", ] [[package]] @@ -310,18 +319,23 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "ctor" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" +[[package]] +name = "divan-macros" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" @@ -524,15 +538,10 @@ dependencies = [ ] [[package]] -name = "half" -version = "2.7.1" +name = "glob" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "halfbrown" @@ -618,9 +627,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -641,6 +650,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -649,9 +664,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -768,6 +783,18 @@ dependencies = [ "libloading", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -795,22 +822,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -823,34 +834,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -861,6 +844,15 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -983,6 +975,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -1104,15 +1102,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "semver" version = "1.0.28" @@ -1258,7 +1247,7 @@ dependencies = [ name = "srcmap-codec" version = "0.3.8" dependencies = [ - "criterion", + "codspeed-divan-compat", "memchr", "rayon", ] @@ -1277,7 +1266,7 @@ dependencies = [ name = "srcmap-generator" version = "0.3.8" dependencies = [ - "criterion", + "codspeed-divan-compat", "rayon", "rustc-hash", "serde_json", @@ -1324,7 +1313,7 @@ dependencies = [ name = "srcmap-remapping" version = "0.3.8" dependencies = [ - "criterion", + "codspeed-divan-compat", "serde_json", "srcmap-generator", "srcmap-sourcemap", @@ -1373,7 +1362,7 @@ dependencies = [ name = "srcmap-sourcemap" version = "0.3.8" dependencies = [ - "criterion", + "codspeed-divan-compat", "memchr", "serde", "serde_json", @@ -1422,6 +1411,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1458,6 +1457,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -1478,16 +1487,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.11.0" @@ -1503,6 +1502,36 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1596,16 +1625,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1709,16 +1728,6 @@ dependencies = [ "semver", ] -[[package]] -name = "web-sys" -version = "0.3.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "1.0.6" @@ -1728,37 +1737,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.2.1" @@ -1847,6 +1825,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 42afa20..a0dc359 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ srcmap-generator = { version = "0.3.8", path = "crates/generator" } srcmap-remapping = { version = "0.3.8", path = "crates/remapping" } srcmap-scopes = { version = "0.3.8", path = "crates/scopes" } srcmap-symbolicate = { version = "0.3.8", path = "crates/symbolicate" } -criterion = { version = "0.8", features = ["html_reports"] } +divan = { package = "codspeed-divan-compat", version = "4.7" } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["raw_value"] } simd-json = "0.17" diff --git a/benchmarks/download-fixtures.mjs b/benchmarks/download-fixtures.mjs index 6df0215..cf9802f 100644 --- a/benchmarks/download-fixtures.mjs +++ b/benchmarks/download-fixtures.mjs @@ -74,7 +74,7 @@ for (const fixture of FIXTURES) { } if (!downloaded) { - console.error(` ${fixture.name}: FAILED — could not download from any URL`); + console.error(` ${fixture.name}: FAILED: could not download from any URL`); console.error(` Tried: ${fixture.urls.join("\n ")}`); allOk = false; } diff --git a/crates/codec/Cargo.toml b/crates/codec/Cargo.toml index 0884b0e..35264d3 100644 --- a/crates/codec/Cargo.toml +++ b/crates/codec/Cargo.toml @@ -25,7 +25,7 @@ memchr = { workspace = true } rayon = { workspace = true, optional = true } [dev-dependencies] -criterion = { workspace = true } +divan = { workspace = true } [[bench]] name = "vlq" diff --git a/crates/codec/benches/vlq.rs b/crates/codec/benches/vlq.rs index 99712aa..70aa2ed 100644 --- a/crates/codec/benches/vlq.rs +++ b/crates/codec/benches/vlq.rs @@ -1,9 +1,13 @@ -use criterion::{Criterion, criterion_group, criterion_main}; +use divan::Bencher; #[cfg(feature = "parallel")] use srcmap_codec::encode_parallel; use srcmap_codec::{Segment, decode, encode}; use std::hint::black_box; +fn main() { + divan::main(); +} + /// Synthetic all-zero mappings (best case: single-char VLQ values). fn make_synthetic_mappings() -> String { let line = std::iter::repeat_n("AAAA", 50).collect::>().join(","); @@ -59,22 +63,23 @@ fn make_realistic_mappings() -> String { encode(&mappings) } -fn bench_decode(c: &mut Criterion) { - let small = "AAAA;AACA,GAAG;AACA,IAAI,EAAE"; - let synthetic = make_synthetic_mappings(); - let realistic = make_realistic_mappings(); - - c.bench_function("decode small", |b| { - b.iter(|| decode(black_box(small)).unwrap()); - }); +#[divan::bench] +fn decode_small() { + decode(black_box("AAAA;AACA,GAAG;AACA,IAAI,EAAE")).unwrap(); +} - c.bench_function("decode synthetic (50K segments)", |b| { - b.iter(|| decode(black_box(&synthetic)).unwrap()); - }); +#[divan::bench] +fn decode_synthetic_50k_segments(bencher: Bencher) { + bencher + .with_inputs(make_synthetic_mappings) + .bench_refs(|mappings| decode(black_box(mappings)).unwrap()); +} - c.bench_function("decode realistic (500 lines)", |b| { - b.iter(|| decode(black_box(&realistic)).unwrap()); - }); +#[divan::bench] +fn decode_realistic_500_lines(bencher: Bencher) { + bencher + .with_inputs(make_realistic_mappings) + .bench_refs(|mappings| decode(black_box(mappings)).unwrap()); } #[cfg(feature = "parallel")] @@ -113,86 +118,99 @@ fn make_large_realistic_mappings() -> srcmap_codec::SourceMapMappings { mappings } -fn bench_encode(c: &mut Criterion) { +fn make_decoded_synthetic_mappings() -> srcmap_codec::SourceMapMappings { let synthetic = make_synthetic_mappings(); - let decoded_synthetic = decode(&synthetic).unwrap(); + decode(&synthetic).unwrap() +} +fn make_decoded_realistic_mappings() -> srcmap_codec::SourceMapMappings { let realistic = make_realistic_mappings(); - let decoded_realistic = decode(&realistic).unwrap(); + decode(&realistic).unwrap() +} - c.bench_function("encode synthetic (50K segments)", |b| { - b.iter(|| encode(black_box(&decoded_synthetic))); - }); +#[divan::bench] +fn encode_synthetic_50k_segments(bencher: Bencher) { + bencher + .with_inputs(make_decoded_synthetic_mappings) + .bench_refs(|mappings| encode(black_box(mappings))); +} - c.bench_function("encode realistic (500 lines)", |b| { - b.iter(|| encode(black_box(&decoded_realistic))); - }); +#[divan::bench] +fn encode_realistic_500_lines(bencher: Bencher) { + bencher + .with_inputs(make_decoded_realistic_mappings) + .bench_refs(|mappings| encode(black_box(mappings))); +} - #[cfg(feature = "parallel")] - { - let large = make_large_realistic_mappings(); - - c.bench_function("encode sequential (5K lines)", |b| { - b.iter(|| encode(black_box(&large))); - }); - - c.bench_function("encode parallel (5K lines)", |b| { - b.iter(|| encode_parallel(black_box(&large))); - }); - - // 50K lines — large enough for parallelism to dominate - let very_large = { - let mut mappings = Vec::with_capacity(50000); - let mut src: i64 = 0; - let mut src_line: i64 = 0; - let mut src_col: i64 = 0; - let mut name: i64 = 0; - for line_idx in 0..50000_i64 { - let segments_per_line = 5 + (line_idx % 20) as usize; - let mut line = Vec::with_capacity(segments_per_line); - let mut gen_col: i64 = 0; - for seg in 0..segments_per_line { - let seg = seg as i64; - gen_col += 2 + (seg * 3) % 28; - if seg % 15 == 0 { - src += 1; - } - src_line += if seg % 7 == 0 { -3 } else { 1 }; - src_line = src_line.max(0); - src_col += (seg * 7 + 3) % 50 - 10; - src_col = src_col.max(0); - if seg % 5 == 0 { - name += 1; - line.push(Segment::five(gen_col, src, src_line, src_col, name)); - } else { - line.push(Segment::four(gen_col, src, src_line, src_col)); - } - } - mappings.push(line); - } - mappings - }; +#[cfg(feature = "parallel")] +#[divan::bench] +fn encode_sequential_5k_lines(bencher: Bencher) { + bencher + .with_inputs(make_large_realistic_mappings) + .bench_refs(|mappings| encode(black_box(mappings))); +} - c.bench_function("encode sequential (50K lines)", |b| { - b.iter(|| encode(black_box(&very_large))); - }); +#[cfg(feature = "parallel")] +#[divan::bench] +fn encode_parallel_5k_lines(bencher: Bencher) { + bencher + .with_inputs(make_large_realistic_mappings) + .bench_refs(|mappings| encode_parallel(black_box(mappings))); +} - c.bench_function("encode parallel (50K lines)", |b| { - b.iter(|| encode_parallel(black_box(&very_large))); - }); +#[cfg(feature = "parallel")] +fn make_very_large_realistic_mappings() -> srcmap_codec::SourceMapMappings { + let mut mappings = Vec::with_capacity(50000); + let mut src: i64 = 0; + let mut src_line: i64 = 0; + let mut src_col: i64 = 0; + let mut name: i64 = 0; + for line_idx in 0..50000_i64 { + let segments_per_line = 5 + (line_idx % 20) as usize; + let mut line = Vec::with_capacity(segments_per_line); + let mut gen_col: i64 = 0; + for seg in 0..segments_per_line { + let seg = seg as i64; + gen_col += 2 + (seg * 3) % 28; + if seg % 15 == 0 { + src += 1; + } + src_line += if seg % 7 == 0 { -3 } else { 1 }; + src_line = src_line.max(0); + src_col += (seg * 7 + 3) % 50 - 10; + src_col = src_col.max(0); + if seg % 5 == 0 { + name += 1; + line.push(Segment::five(gen_col, src, src_line, src_col, name)); + } else { + line.push(Segment::four(gen_col, src, src_line, src_col)); + } + } + mappings.push(line); } + mappings } -fn bench_roundtrip(c: &mut Criterion) { - let realistic = make_realistic_mappings(); +#[cfg(feature = "parallel")] +#[divan::bench] +fn encode_sequential_50k_lines(bencher: Bencher) { + bencher + .with_inputs(make_very_large_realistic_mappings) + .bench_refs(|mappings| encode(black_box(mappings))); +} - c.bench_function("roundtrip realistic (500 lines)", |b| { - b.iter(|| { - let decoded = decode(black_box(&realistic)).unwrap(); - encode(black_box(&decoded)) - }); - }); +#[cfg(feature = "parallel")] +#[divan::bench] +fn encode_parallel_50k_lines(bencher: Bencher) { + bencher + .with_inputs(make_very_large_realistic_mappings) + .bench_refs(|mappings| encode_parallel(black_box(mappings))); } -criterion_group!(benches, bench_decode, bench_encode, bench_roundtrip); -criterion_main!(benches); +#[divan::bench] +fn roundtrip_realistic_500_lines(bencher: Bencher) { + bencher.with_inputs(make_realistic_mappings).bench_refs(|realistic| { + let decoded = decode(black_box(realistic)).unwrap(); + encode(black_box(&decoded)) + }); +} diff --git a/crates/generator/Cargo.toml b/crates/generator/Cargo.toml index 8bbedb7..6b8930f 100644 --- a/crates/generator/Cargo.toml +++ b/crates/generator/Cargo.toml @@ -29,7 +29,7 @@ rustc-hash = { workspace = true } [dev-dependencies] serde_json = { workspace = true } -criterion = { workspace = true } +divan = { workspace = true } [[bench]] name = "generate" diff --git a/crates/generator/benches/generate.rs b/crates/generator/benches/generate.rs index 8992563..1a3c6f5 100644 --- a/crates/generator/benches/generate.rs +++ b/crates/generator/benches/generate.rs @@ -1,8 +1,12 @@ use std::hint::black_box; -use criterion::{Criterion, criterion_group, criterion_main}; +use divan::Bencher; use srcmap_generator::{SourceMapGenerator, StreamingGenerator}; +fn main() { + divan::main(); +} + fn build_generator(lines: u32, cols_per_line: u32, with_content: bool) -> SourceMapGenerator { let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string())); for i in 0..10 { @@ -32,78 +36,82 @@ fn build_generator(lines: u32, cols_per_line: u32, with_content: bool) -> Source builder } -fn bench_generate(c: &mut Criterion) { - let mut group = c.benchmark_group("generate"); +fn build_sorted_generator(lines: u32, cols_per_line: u32) -> SourceMapGenerator { + let mut builder = build_generator(lines, cols_per_line, false); + builder.set_assume_sorted(true); + builder +} - group.bench_function("1000 mappings", |b| { - let builder = build_generator(100, 10, false); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_1000_mappings(bencher: Bencher) { + bencher + .with_inputs(|| build_generator(100, 10, false)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("10000 mappings", |b| { - let builder = build_generator(500, 20, false); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_10000_mappings(bencher: Bencher) { + bencher + .with_inputs(|| build_generator(500, 20, false)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("100000 mappings", |b| { - let builder = build_generator(5000, 20, false); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_100000_mappings(bencher: Bencher) { + bencher + .with_inputs(|| build_generator(5000, 20, false)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("100000 mappings + sourcesContent", |b| { - let builder = build_generator(5000, 20, true); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_100000_mappings_with_sources_content(bencher: Bencher) { + bencher + .with_inputs(|| build_generator(5000, 20, true)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("1000 mappings (assume_sorted)", |b| { - let mut builder = build_generator(100, 10, false); - builder.set_assume_sorted(true); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_1000_mappings_assume_sorted(bencher: Bencher) { + bencher + .with_inputs(|| build_sorted_generator(100, 10)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("10000 mappings (assume_sorted)", |b| { - let mut builder = build_generator(500, 20, false); - builder.set_assume_sorted(true); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_10000_mappings_assume_sorted(bencher: Bencher) { + bencher + .with_inputs(|| build_sorted_generator(500, 20)) + .bench_refs(|builder| black_box(builder.to_json())); +} - group.bench_function("100000 mappings (assume_sorted)", |b| { - let mut builder = build_generator(5000, 20, false); - builder.set_assume_sorted(true); - b.iter(|| black_box(builder.to_json())) - }); +#[divan::bench] +fn generate_100000_mappings_assume_sorted(bencher: Bencher) { + bencher + .with_inputs(|| build_sorted_generator(5000, 20)) + .bench_refs(|builder| black_box(builder.to_json())); +} - // StreamingGenerator benchmarks — measures construction + encoding together - // since StreamingGenerator encodes VLQ incrementally during add_mapping calls - group.bench_function("100000 mappings (streaming, construct+encode)", |b| { - b.iter(|| { - let mut sg = StreamingGenerator::new(Some("bundle.js".to_string())); - for i in 0..10 { - let src = sg.add_source(&format!("src/file{i}.js")); - let _ = src; - } - for i in 0..20 { - sg.add_name(&format!("var{i}")); - } - let lines = 5000u32; - let cols_per_line = 20u32; - for line in 0..lines { - for col in 0..cols_per_line { - let src = (line * cols_per_line + col) % 10; - if col % 3 == 0 { - let name = col % 20; - sg.add_named_mapping(line, col * 10, src, line, col * 5, name); - } else { - sg.add_mapping(line, col * 10, src, line, col * 5); - } - } +#[divan::bench] +fn generate_100000_mappings_streaming_construct_encode() { + let mut sg = StreamingGenerator::new(Some("bundle.js".to_string())); + for i in 0..10 { + sg.add_source(&format!("src/file{i}.js")); + } + for i in 0..20 { + sg.add_name(&format!("var{i}")); + } + let lines = 5000u32; + let cols_per_line = 20u32; + for line in 0..lines { + for col in 0..cols_per_line { + let src = (line * cols_per_line + col) % 10; + if col % 3 == 0 { + let name = col % 20; + sg.add_named_mapping(line, col * 10, src, line, col * 5, name); + } else { + sg.add_mapping(line, col * 10, src, line, col * 5); } - black_box(sg.to_json()) - }) - }); - - group.finish(); + } + } + black_box(sg.to_json()); } - -criterion_group!(benches, bench_generate); -criterion_main!(benches); diff --git a/crates/ram-bundle/src/lib.rs b/crates/ram-bundle/src/lib.rs index 706d3f9..b35cfb8 100644 --- a/crates/ram-bundle/src/lib.rs +++ b/crates/ram-bundle/src/lib.rs @@ -250,9 +250,7 @@ fn parse_startup_code( startup_end: usize, ) -> Result { std::str::from_utf8(&data[startup_start..startup_end]) - .map_err(|e| { - RamBundleError::InvalidEntry(format!("startup code is not valid UTF-8: {e}")) - }) + .map_err(|e| RamBundleError::InvalidEntry(format!("startup code is not valid UTF-8: {e}"))) .map(str::to_owned) } diff --git a/crates/remapping/Cargo.toml b/crates/remapping/Cargo.toml index 4c0664b..b9c3b42 100644 --- a/crates/remapping/Cargo.toml +++ b/crates/remapping/Cargo.toml @@ -21,7 +21,7 @@ srcmap-sourcemap = { workspace = true } srcmap-generator = { workspace = true } [dev-dependencies] -criterion = { workspace = true } +divan = { workspace = true } serde_json = { workspace = true } [[bench]] diff --git a/crates/remapping/benches/remap.rs b/crates/remapping/benches/remap.rs index 3ae0048..ac34408 100644 --- a/crates/remapping/benches/remap.rs +++ b/crates/remapping/benches/remap.rs @@ -1,10 +1,20 @@ use std::hint::black_box; -use criterion::{Criterion, criterion_group, criterion_main}; +use divan::Bencher; use srcmap_generator::SourceMapGenerator; use srcmap_remapping::{remap, remap_streaming}; use srcmap_sourcemap::{MappingsIter, SourceMap}; +fn main() { + divan::main(); +} + +struct ChainInput { + outer: SourceMap, + inner: SourceMap, + vlq: String, +} + fn build_chain(mapping_count: u32) -> (SourceMap, SourceMap) { // Outer: generated -> intermediate let mut outer_gen = SourceMapGenerator::new(Some("output.js".to_string())); @@ -29,41 +39,69 @@ fn build_chain(mapping_count: u32) -> (SourceMap, SourceMap) { (outer, inner) } -fn bench_remap(c: &mut Criterion) { - for &count in &[500u32, 10_000, 60_000] { - let (outer, inner) = build_chain(count); - - // Pre-encode VLQ for the streaming variant - let vlq = outer.encode_mappings(); - - c.bench_function(&format!("remap_{count}"), |b| { - b.iter(|| { - black_box(remap(&outer, |_| Some(inner.clone()))); - }) - }); - - c.bench_function(&format!("remap_streaming_{count}"), |b| { - b.iter(|| { - let iter = MappingsIter::new(&vlq); - black_box(remap_streaming( - iter, - &outer.sources, - &outer.names, - &outer.sources_content, - &outer.ignore_list, - outer.file.clone(), - |_| Some(inner.clone()), - )); - }) - }); - } +fn build_chain_input(mapping_count: u32) -> ChainInput { + let (outer, inner) = build_chain(mapping_count); + let vlq = outer.encode_mappings(); + + ChainInput { outer, inner, vlq } +} + +fn bench_remap_input(input: &mut ChainInput) { + black_box(remap(&input.outer, |_| Some(input.inner.clone()))); +} + +fn bench_remap_streaming_input(input: &mut ChainInput) { + let iter = MappingsIter::new(&input.vlq); + black_box(remap_streaming( + iter, + &input.outer.sources, + &input.outer.names, + &input.outer.sources_content, + &input.outer.ignore_list, + input.outer.file.clone(), + |_| Some(input.inner.clone()), + )); +} + +#[divan::bench] +fn remap_500(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(500)).bench_refs(bench_remap_input); +} + +#[divan::bench] +fn remap_streaming_500(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(500)).bench_refs(bench_remap_streaming_input); } -/// Simulate a bundler workload: multiple source files each with their own -/// source map, composed through a single bundler output map. -fn bench_remap_bundler(c: &mut Criterion) { +#[divan::bench] +fn remap_10000(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(10_000)).bench_refs(bench_remap_input); +} + +#[divan::bench] +fn remap_streaming_10000(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(10_000)).bench_refs(bench_remap_streaming_input); +} + +#[divan::bench] +fn remap_60000(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(60_000)).bench_refs(bench_remap_input); +} + +#[divan::bench] +fn remap_streaming_60000(bencher: Bencher) { + bencher.with_inputs(|| build_chain_input(60_000)).bench_refs(bench_remap_streaming_input); +} + +struct BundlerInput { + outer: SourceMap, + vlq: String, + inner_maps: Vec<(String, SourceMap)>, +} + +fn build_bundler_input() -> BundlerInput { let source_count = 20; - let mappings_per_source = 3000; // 60K total + let mappings_per_source = 3000; // Build inner maps (one per source file, simulating TS → JS transforms) let inner_maps: Vec<(String, SourceMap)> = (0..source_count) @@ -96,31 +134,32 @@ fn bench_remap_bundler(c: &mut Criterion) { let outer = outer_gen.to_decoded_map(); let vlq = outer.encode_mappings(); - c.bench_function("remap_bundler_60k_20src", |b| { - b.iter(|| { - black_box(remap(&outer, |source| { - inner_maps.iter().find(|(name, _)| name == source).map(|(_, sm)| sm.clone()) - })); - }) - }); + BundlerInput { outer, vlq, inner_maps } +} - c.bench_function("remap_streaming_bundler_60k_20src", |b| { - b.iter(|| { - let iter = MappingsIter::new(&vlq); - black_box(remap_streaming( - iter, - &outer.sources, - &outer.names, - &outer.sources_content, - &outer.ignore_list, - outer.file.clone(), - |source| { - inner_maps.iter().find(|(name, _)| name == source).map(|(_, sm)| sm.clone()) - }, - )); - }) +#[divan::bench] +fn remap_bundler_60k_20src(bencher: Bencher) { + bencher.with_inputs(build_bundler_input).bench_refs(|input| { + black_box(remap(&input.outer, |source| { + input.inner_maps.iter().find(|(name, _)| name == source).map(|(_, sm)| sm.clone()) + })); }); } -criterion_group!(benches, bench_remap, bench_remap_bundler); -criterion_main!(benches); +#[divan::bench] +fn remap_streaming_bundler_60k_20src(bencher: Bencher) { + bencher.with_inputs(build_bundler_input).bench_refs(|input| { + let iter = MappingsIter::new(&input.vlq); + black_box(remap_streaming( + iter, + &input.outer.sources, + &input.outer.names, + &input.outer.sources_content, + &input.outer.ignore_list, + input.outer.file.clone(), + |source| { + input.inner_maps.iter().find(|(name, _)| name == source).map(|(_, sm)| sm.clone()) + }, + )); + }); +} diff --git a/crates/remapping/src/lib.rs b/crates/remapping/src/lib.rs index 05c6012..a15d703 100644 --- a/crates/remapping/src/lib.rs +++ b/crates/remapping/src/lib.rs @@ -1085,7 +1085,8 @@ fn load_streaming_source_entry( match loader(source_name) { Some(upstream_sm) => { let cache = build_upstream_cache(&upstream_sm); - source_entries[si] = StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache }; + source_entries[si] = + StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache }; } None => { let idx = ctx.builder.add_source(source_name); diff --git a/crates/sourcemap/Cargo.toml b/crates/sourcemap/Cargo.toml index 690bcff..233f084 100644 --- a/crates/sourcemap/Cargo.toml +++ b/crates/sourcemap/Cargo.toml @@ -24,7 +24,7 @@ serde_json = { workspace = true } memchr = { workspace = true } [dev-dependencies] -criterion = { workspace = true } +divan = { workspace = true } simd-json = { workspace = true } sonic-rs = { workspace = true } diff --git a/crates/sourcemap/benches/parse.rs b/crates/sourcemap/benches/parse.rs index d66bf16..2dd7a95 100644 --- a/crates/sourcemap/benches/parse.rs +++ b/crates/sourcemap/benches/parse.rs @@ -1,16 +1,20 @@ use std::hint::black_box; -use criterion::{Criterion, criterion_group, criterion_main}; +use divan::Bencher; use serde::Deserialize; use srcmap_codec::Segment; use srcmap_sourcemap::{LazySourceMap, SourceMap}; -// ── Prototype: simd-json / sonic-rs parse paths ──────────────────── +fn main() { + divan::main(); +} + +// Prototype: simd-json / sonic-rs parse paths. // // These mirror the regular-source-map hot path in `SourceMap::from_json`, // but swap out the JSON decoder. They omit features that neither simd-json // nor sonic-rs handle cleanly (sections via RawValue, #[serde(flatten)] -// extensions) — those cases fall through to serde_json in production. +// extensions), so those cases fall through to serde_json in production. // // The goal is to measure whether a SIMD JSON parse meaningfully changes // end-to-end parse time once you add back the VLQ decode work. @@ -197,89 +201,169 @@ fn clone_via_from_parts(sm: &SourceMap) -> SourceMap { ) } -fn bench_parse(c: &mut Criterion) { - let small = generate_sourcemap_json(50, 10, 3); - let medium = generate_sourcemap_json(500, 20, 5); - let large = generate_sourcemap_json(2000, 50, 10); - let large_no_content = generate_sourcemap_json_no_content(2000, 50, 10); +fn json_small() -> String { + generate_sourcemap_json(50, 10, 3) +} - let mut group = c.benchmark_group("parse"); +fn json_medium() -> String { + generate_sourcemap_json(500, 20, 5) +} - group.bench_function("small (500 segs)", |b| { - b.iter(|| SourceMap::from_json(black_box(&small)).unwrap()) - }); +fn json_large() -> String { + generate_sourcemap_json(2000, 50, 10) +} - group.bench_function("medium (10K segs)", |b| { - b.iter(|| SourceMap::from_json(black_box(&medium)).unwrap()) - }); +fn json_large_no_content() -> String { + generate_sourcemap_json_no_content(2000, 50, 10) +} - group.bench_function("large (100K segs)", |b| { - b.iter(|| SourceMap::from_json(black_box(&large)).unwrap()) - }); +#[divan::bench] +fn parse_small_500_segments(bencher: Bencher) { + bencher + .with_inputs(json_small) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} - group.bench_function("large no sourcesContent", |b| { - b.iter(|| SourceMap::from_json(black_box(&large_no_content)).unwrap()) - }); +#[divan::bench] +fn parse_medium_10k_segments(bencher: Bencher) { + bencher + .with_inputs(json_medium) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} - group.finish(); -} - -/// Interop benchmark for producers that already have decoded source-map parts. -/// This compares the old JSON interchange path with the structured constructor. -fn bench_from_parts_interop(c: &mut Criterion) { - let small = SourceMap::from_json(&generate_sourcemap_json(50, 10, 3)).unwrap(); - let medium = SourceMap::from_json(&generate_sourcemap_json(500, 20, 5)).unwrap(); - let large = SourceMap::from_json(&generate_sourcemap_json(2000, 50, 10)).unwrap(); - let fixtures = - [("small (500 segs)", small), ("medium (10K segs)", medium), ("large (100K segs)", large)]; - - let mut group = c.benchmark_group("from_parts_interop"); - - for (label, sm) in &fixtures { - group.bench_function(format!("{label} / JSON serialize + parse"), |b| { - b.iter(|| { - let json = black_box(sm).to_json(); - SourceMap::from_json(black_box(&json)).unwrap() - }) - }); - group.bench_function(format!("{label} / from_parts"), |b| { - b.iter(|| clone_via_from_parts(black_box(sm))) - }); - } +#[divan::bench] +fn parse_large_100k_segments(bencher: Bencher) { + bencher + .with_inputs(json_large) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} - group.finish(); -} - -/// End-to-end parse comparison: current `SourceMap::from_json` (serde_json with -/// the full `RawSourceMap` struct including `#[serde(flatten)]` extensions) vs -/// the three minimal-struct paths backed by serde_json, simd-json, and sonic-rs. -fn bench_parse_backends(c: &mut Criterion) { - let medium = generate_sourcemap_json(500, 20, 5); - let large = generate_sourcemap_json(2000, 50, 10); - let large_no_content = generate_sourcemap_json_no_content(2000, 50, 10); - - let mut group = c.benchmark_group("parse_backends"); - - for (label, json) in [ - ("medium (10K segs)", &medium), - ("large (100K segs)", &large), - ("large no sourcesContent", &large_no_content), - ] { - group.bench_function(format!("{label} / serde_json (current)"), |b| { - b.iter(|| SourceMap::from_json(black_box(json)).unwrap()) - }); - group.bench_function(format!("{label} / serde_json (minimal struct)"), |b| { - b.iter(|| parse_with_serde_json_minimal(black_box(json))) - }); - group.bench_function(format!("{label} / simd-json"), |b| { - b.iter(|| parse_with_simd_json(black_box(json))) - }); - group.bench_function(format!("{label} / sonic-rs"), |b| { - b.iter(|| parse_with_sonic_rs(black_box(json))) - }); - } +#[divan::bench] +fn parse_large_no_sources_content(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +fn sourcemap_from_json_input(lines: usize, segs_per_line: usize, num_sources: usize) -> SourceMap { + SourceMap::from_json(&generate_sourcemap_json(lines, segs_per_line, num_sources)).unwrap() +} + +fn json_roundtrip(sm: &mut SourceMap) -> SourceMap { + let json = black_box(sm).to_json(); + SourceMap::from_json(black_box(&json)).unwrap() +} + +#[divan::bench] +fn from_parts_interop_small_json_roundtrip(bencher: Bencher) { + bencher.with_inputs(|| sourcemap_from_json_input(50, 10, 3)).bench_refs(json_roundtrip); +} + +#[divan::bench] +fn from_parts_interop_small_from_parts(bencher: Bencher) { + bencher + .with_inputs(|| sourcemap_from_json_input(50, 10, 3)) + .bench_refs(|sm| clone_via_from_parts(black_box(sm))); +} + +#[divan::bench] +fn from_parts_interop_medium_json_roundtrip(bencher: Bencher) { + bencher.with_inputs(|| sourcemap_from_json_input(500, 20, 5)).bench_refs(json_roundtrip); +} + +#[divan::bench] +fn from_parts_interop_medium_from_parts(bencher: Bencher) { + bencher + .with_inputs(|| sourcemap_from_json_input(500, 20, 5)) + .bench_refs(|sm| clone_via_from_parts(black_box(sm))); +} + +#[divan::bench] +fn from_parts_interop_large_json_roundtrip(bencher: Bencher) { + bencher.with_inputs(|| sourcemap_from_json_input(2000, 50, 10)).bench_refs(json_roundtrip); +} + +#[divan::bench] +fn from_parts_interop_large_from_parts(bencher: Bencher) { + bencher + .with_inputs(|| sourcemap_from_json_input(2000, 50, 10)) + .bench_refs(|sm| clone_via_from_parts(black_box(sm))); +} + +#[divan::bench] +fn parse_backends_medium_current(bencher: Bencher) { + bencher + .with_inputs(json_medium) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +#[divan::bench] +fn parse_backends_medium_serde_json_minimal(bencher: Bencher) { + bencher + .with_inputs(json_medium) + .bench_refs(|json| parse_with_serde_json_minimal(black_box(json))); +} + +#[divan::bench] +fn parse_backends_medium_simd_json(bencher: Bencher) { + bencher.with_inputs(json_medium).bench_refs(|json| parse_with_simd_json(black_box(json))); +} + +#[divan::bench] +fn parse_backends_medium_sonic_rs(bencher: Bencher) { + bencher.with_inputs(json_medium).bench_refs(|json| parse_with_sonic_rs(black_box(json))); +} + +#[divan::bench] +fn parse_backends_large_current(bencher: Bencher) { + bencher + .with_inputs(json_large) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +#[divan::bench] +fn parse_backends_large_serde_json_minimal(bencher: Bencher) { + bencher + .with_inputs(json_large) + .bench_refs(|json| parse_with_serde_json_minimal(black_box(json))); +} - group.finish(); +#[divan::bench] +fn parse_backends_large_simd_json(bencher: Bencher) { + bencher.with_inputs(json_large).bench_refs(|json| parse_with_simd_json(black_box(json))); +} + +#[divan::bench] +fn parse_backends_large_sonic_rs(bencher: Bencher) { + bencher.with_inputs(json_large).bench_refs(|json| parse_with_sonic_rs(black_box(json))); +} + +#[divan::bench] +fn parse_backends_large_no_content_current(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +#[divan::bench] +fn parse_backends_large_no_content_serde_json_minimal(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| parse_with_serde_json_minimal(black_box(json))); +} + +#[divan::bench] +fn parse_backends_large_no_content_simd_json(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| parse_with_simd_json(black_box(json))); +} + +#[divan::bench] +fn parse_backends_large_no_content_sonic_rs(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| parse_with_sonic_rs(black_box(json))); } /// Real-world source map fixtures. Falls back to synthetic maps if @@ -289,85 +373,143 @@ fn load_fixture(name: &str) -> Option { std::fs::read_to_string(&path).ok() } -/// Real-world parse: chartjs / preact / pdfjs. Isolates the concrete workload -/// so we know whether synthetic generator results translate to real data. -fn bench_real_world(c: &mut Criterion) { - let fixtures: Vec<(&str, String)> = ["preact", "chartjs", "pdfjs"] - .iter() - .filter_map(|name| load_fixture(name).map(|s| (*name, s))) - .collect(); +fn fixture_or_synthetic(name: &str) -> String { + load_fixture(name).unwrap_or_else(json_large_no_content) +} - if fixtures.is_empty() { - return; - } +fn fixture_preact() -> String { + fixture_or_synthetic("preact") +} - let mut group = c.benchmark_group("real_world"); - - for (name, json) in &fixtures { - let kb = json.len() / 1024; - let label = format!("{name} ({kb} KB)"); - - group.bench_function(format!("{label} / serde_json (current)"), |b| { - b.iter(|| SourceMap::from_json(black_box(json)).unwrap()) - }); - group.bench_function(format!("{label} / sonic-rs"), |b| { - b.iter(|| parse_with_sonic_rs(black_box(json))) - }); - group.bench_function(format!("{label} / simd-json"), |b| { - b.iter(|| parse_with_simd_json(black_box(json))) - }); - } +fn fixture_chartjs() -> String { + fixture_or_synthetic("chartjs") +} - group.finish(); +fn fixture_pdfjs() -> String { + fixture_or_synthetic("pdfjs") } -/// Lite-path parses (skip sourcesContent allocation). These hit the -/// `RawSourceMapLite` codepath used by the WASM bindings — the round-3 -/// sonic-rs migration target. -fn bench_lite_paths(c: &mut Criterion) { - let fixtures: Vec<(&str, String)> = ["preact", "chartjs", "pdfjs"] - .iter() - .filter_map(|name| load_fixture(name).map(|s| (*name, s))) - .collect(); +#[divan::bench] +fn real_world_preact_current(bencher: Bencher) { + bencher + .with_inputs(fixture_preact) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} - if fixtures.is_empty() { - return; - } +#[divan::bench] +fn real_world_preact_sonic_rs(bencher: Bencher) { + bencher.with_inputs(fixture_preact).bench_refs(|json| parse_with_sonic_rs(black_box(json))); +} - let mut group = c.benchmark_group("lite_paths"); - - for (name, json) in &fixtures { - let kb = json.len() / 1024; - let label = format!("{name} ({kb} KB)"); - - group.bench_function(format!("{label} / SourceMap::from_json_no_content"), |b| { - b.iter(|| SourceMap::from_json_no_content(black_box(json)).unwrap()) - }); - group.bench_function(format!("{label} / LazySourceMap::from_json_no_content"), |b| { - b.iter(|| LazySourceMap::from_json_no_content(black_box(json)).unwrap()) - }); - group.bench_function(format!("{label} / LazySourceMap::from_json_fast"), |b| { - b.iter(|| LazySourceMap::from_json_fast(black_box(json)).unwrap()) - }); - } +#[divan::bench] +fn real_world_preact_simd_json(bencher: Bencher) { + bencher.with_inputs(fixture_preact).bench_refs(|json| parse_with_simd_json(black_box(json))); +} - group.finish(); +#[divan::bench] +fn real_world_chartjs_current(bencher: Bencher) { + bencher + .with_inputs(fixture_chartjs) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); } -/// Pure VLQ decode isolation: how much of the total parse time is the -/// VLQ pass, separate from JSON decode and struct construction? -fn bench_vlq_isolation(c: &mut Criterion) { - let fixtures: Vec<(&str, String)> = ["preact", "chartjs", "pdfjs"] - .iter() - .filter_map(|name| load_fixture(name).map(|s| (*name, s))) - .collect(); +#[divan::bench] +fn real_world_chartjs_sonic_rs(bencher: Bencher) { + bencher.with_inputs(fixture_chartjs).bench_refs(|json| parse_with_sonic_rs(black_box(json))); +} - if fixtures.is_empty() { - return; - } +#[divan::bench] +fn real_world_chartjs_simd_json(bencher: Bencher) { + bencher.with_inputs(fixture_chartjs).bench_refs(|json| parse_with_simd_json(black_box(json))); +} - // Pre-extract the mappings string (and sources, names) from each fixture - // so the VLQ bench doesn't include JSON parse overhead. +#[divan::bench] +fn real_world_pdfjs_current(bencher: Bencher) { + bencher + .with_inputs(fixture_pdfjs) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +#[divan::bench] +fn real_world_pdfjs_sonic_rs(bencher: Bencher) { + bencher.with_inputs(fixture_pdfjs).bench_refs(|json| parse_with_sonic_rs(black_box(json))); +} + +#[divan::bench] +fn real_world_pdfjs_simd_json(bencher: Bencher) { + bencher.with_inputs(fixture_pdfjs).bench_refs(|json| parse_with_simd_json(black_box(json))); +} + +#[divan::bench] +fn lite_paths_preact_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_preact) + .bench_refs(|json| SourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_preact_lazy_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_preact) + .bench_refs(|json| LazySourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_preact_lazy_fast(bencher: Bencher) { + bencher + .with_inputs(fixture_preact) + .bench_refs(|json| LazySourceMap::from_json_fast(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_chartjs_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_chartjs) + .bench_refs(|json| SourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_chartjs_lazy_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_chartjs) + .bench_refs(|json| LazySourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_chartjs_lazy_fast(bencher: Bencher) { + bencher + .with_inputs(fixture_chartjs) + .bench_refs(|json| LazySourceMap::from_json_fast(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_pdfjs_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_pdfjs) + .bench_refs(|json| SourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_pdfjs_lazy_no_content(bencher: Bencher) { + bencher + .with_inputs(fixture_pdfjs) + .bench_refs(|json| LazySourceMap::from_json_no_content(black_box(json)).unwrap()); +} + +#[divan::bench] +fn lite_paths_pdfjs_lazy_fast(bencher: Bencher) { + bencher + .with_inputs(fixture_pdfjs) + .bench_refs(|json| LazySourceMap::from_json_fast(black_box(json)).unwrap()); +} + +struct VlqFixture { + mappings: String, + sources: Vec, + names: Vec, +} + +fn vlq_fixture(name: &str) -> VlqFixture { #[derive(Deserialize)] struct JustMappings { #[serde(default)] @@ -378,129 +520,132 @@ fn bench_vlq_isolation(c: &mut Criterion) { names: Vec, } - let extracted: Vec<(String, String, Vec, Vec)> = fixtures - .iter() - .map(|(name, json)| { - let jm: JustMappings = serde_json::from_str(json).unwrap(); - let sources: Vec = - jm.sources.into_iter().map(|s| s.unwrap_or_default()).collect(); - (name.to_string(), jm.mappings, sources, jm.names) - }) - .collect(); + let json = fixture_or_synthetic(name); + let jm: JustMappings = serde_json::from_str(&json).unwrap(); + let sources = jm.sources.into_iter().map(|s| s.unwrap_or_default()).collect(); - let mut group = c.benchmark_group("vlq_isolation"); - - for (name, mappings_str, sources, names) in &extracted { - let kb = mappings_str.len() / 1024; - let label = format!("{name} mappings ({kb} KB)"); - - group.bench_function(format!("{label} / codec::decode (segments)"), |b| { - b.iter(|| srcmap_codec::decode(black_box(mappings_str)).unwrap()) - }); - - // End-to-end: mappings string -> full SourceMap via the sourcemap-crate - // decoder (measures the specialized `decode_mappings` + struct assembly). - group.bench_function(format!("{label} / SourceMap::from_vlq"), |b| { - b.iter(|| { - SourceMap::from_vlq( - black_box(mappings_str), - sources.clone(), - names.clone(), - None, - None, - Vec::new(), - Vec::new(), - None, - ) - .unwrap() - }) - }); - } + VlqFixture { mappings: jm.mappings, sources, names: jm.names } +} - group.finish(); +fn bench_codec_decode(input: &mut VlqFixture) { + srcmap_codec::decode(black_box(&input.mappings)).unwrap(); } -/// JSON-only parse comparison: isolate the JSON decoder from VLQ decoding. -fn bench_json_only(c: &mut Criterion) { - let large = generate_sourcemap_json_no_content(2000, 50, 10); +fn bench_source_map_from_vlq(input: &mut VlqFixture) { + SourceMap::from_vlq( + black_box(&input.mappings), + input.sources.clone(), + input.names.clone(), + None, + None, + Vec::new(), + Vec::new(), + None, + ) + .unwrap(); +} - let mut group = c.benchmark_group("json_only"); +#[divan::bench] +fn vlq_isolation_preact_codec_decode(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("preact")).bench_refs(bench_codec_decode); +} - group.bench_function("serde_json -> Value", |b| { - b.iter(|| { - let _: serde_json::Value = serde_json::from_str(black_box(&large)).unwrap(); - }) - }); - group.bench_function("serde_json -> MinimalRawSourceMap", |b| { - b.iter(|| { - let _: MinimalRawSourceMap<'_> = serde_json::from_str(black_box(&large)).unwrap(); - }) - }); - group.bench_function("simd-json -> MinimalRawSourceMap", |b| { - b.iter(|| { - let mut bytes = large.as_bytes().to_vec(); - let _: MinimalRawSourceMap<'_> = simd_json::serde::from_slice(&mut bytes).unwrap(); - }) - }); - group.bench_function("sonic-rs -> MinimalRawSourceMap", |b| { - b.iter(|| { - let _: MinimalRawSourceMap<'_> = sonic_rs::from_str(black_box(&large)).unwrap(); - }) - }); +#[divan::bench] +fn vlq_isolation_preact_source_map_from_vlq(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("preact")).bench_refs(bench_source_map_from_vlq); +} - group.finish(); +#[divan::bench] +fn vlq_isolation_chartjs_codec_decode(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("chartjs")).bench_refs(bench_codec_decode); } -fn bench_lookup(c: &mut Criterion) { - let medium = generate_sourcemap_json(500, 20, 5); - let sm = SourceMap::from_json(&medium).unwrap(); +#[divan::bench] +fn vlq_isolation_chartjs_source_map_from_vlq(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("chartjs")).bench_refs(bench_source_map_from_vlq); +} - let mut group = c.benchmark_group("lookup"); +#[divan::bench] +fn vlq_isolation_pdfjs_codec_decode(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("pdfjs")).bench_refs(bench_codec_decode); +} + +#[divan::bench] +fn vlq_isolation_pdfjs_source_map_from_vlq(bencher: Bencher) { + bencher.with_inputs(|| vlq_fixture("pdfjs")).bench_refs(bench_source_map_from_vlq); +} - group.bench_function("single original_position_for", |b| { - b.iter(|| sm.original_position_for(black_box(250), black_box(30))) +#[divan::bench] +fn json_only_serde_json_value(bencher: Bencher) { + bencher.with_inputs(json_large_no_content).bench_refs(|json| { + let _: serde_json::Value = serde_json::from_str(black_box(json)).unwrap(); }); +} - group.bench_function("1000x original_position_for", |b| { - let lookups: Vec<(u32, u32)> = (0..1000).map(|i| ((i * 7) % 500, (i * 13) % 200)).collect(); - b.iter(|| { - for &(line, col) in &lookups { - black_box(sm.original_position_for(line, col)); - } - }) +#[divan::bench] +fn json_only_serde_json_minimal(bencher: Bencher) { + bencher.with_inputs(json_large_no_content).bench_refs(|json| { + let _: MinimalRawSourceMap<'_> = serde_json::from_str(black_box(json)).unwrap(); + }); +} + +#[divan::bench] +fn json_only_simd_json_minimal(bencher: Bencher) { + bencher.with_inputs(json_large_no_content).bench_refs(|json| { + let mut bytes = json.as_bytes().to_vec(); + let _: MinimalRawSourceMap<'_> = simd_json::serde::from_slice(&mut bytes).unwrap(); }); +} - group.finish(); +#[divan::bench] +fn json_only_sonic_rs_minimal(bencher: Bencher) { + bencher.with_inputs(json_large_no_content).bench_refs(|json| { + let _: MinimalRawSourceMap<'_> = sonic_rs::from_str(black_box(json)).unwrap(); + }); } -fn bench_vlq_only(c: &mut Criterion) { - let json = generate_sourcemap_json_no_content(2000, 50, 10); +fn lookup_input() -> SourceMap { + SourceMap::from_json(&json_medium()).unwrap() +} - let mut group = c.benchmark_group("vlq_decode"); +#[divan::bench] +fn lookup_single_original_position_for(bencher: Bencher) { + bencher + .with_inputs(lookup_input) + .bench_refs(|sm| sm.original_position_for(black_box(250), black_box(30))); +} - group.bench_function("large mappings only", |b| { - b.iter(|| srcmap_sourcemap::SourceMap::from_json(black_box(&json)).unwrap()) - }); +struct BatchLookupInput { + sm: SourceMap, + lookups: Vec<(u32, u32)>, +} - group.bench_function("serde_json parse only", |b| { - b.iter(|| { - let _: serde_json::Value = serde_json::from_str(black_box(&json)).unwrap(); - }) +fn batch_lookup_input() -> BatchLookupInput { + let sm = lookup_input(); + let lookups = (0..1000).map(|i| ((i * 7) % 500, (i * 13) % 200)).collect(); + + BatchLookupInput { sm, lookups } +} + +#[divan::bench] +fn lookup_1000x_original_position_for(bencher: Bencher) { + bencher.with_inputs(batch_lookup_input).bench_refs(|input| { + for &(line, col) in &input.lookups { + black_box(input.sm.original_position_for(line, col)); + } }); +} - group.finish(); -} - -criterion_group!( - benches, - bench_parse, - bench_from_parts_interop, - bench_lookup, - bench_vlq_only, - bench_parse_backends, - bench_json_only, - bench_real_world, - bench_lite_paths, - bench_vlq_isolation -); -criterion_main!(benches); +#[divan::bench] +fn vlq_decode_large_mappings_only(bencher: Bencher) { + bencher + .with_inputs(json_large_no_content) + .bench_refs(|json| SourceMap::from_json(black_box(json)).unwrap()); +} + +#[divan::bench] +fn vlq_decode_serde_json_parse_only(bencher: Bencher) { + bencher.with_inputs(json_large_no_content).bench_refs(|json| { + let _: serde_json::Value = serde_json::from_str(black_box(json)).unwrap(); + }); +} diff --git a/crates/sourcemap/src/lib.rs b/crates/sourcemap/src/lib.rs index 3366afd..3036ea5 100644 --- a/crates/sourcemap/src/lib.rs +++ b/crates/sourcemap/src/lib.rs @@ -239,9 +239,7 @@ fn build_mapping_line_offsets(mappings: &[Mapping], line_count: usize) -> Vec Vec { mappings.sort_unstable_by(|a, b| { - a.generated_line - .cmp(&b.generated_line) - .then(a.generated_column.cmp(&b.generated_column)) + a.generated_line.cmp(&b.generated_line).then(a.generated_column.cmp(&b.generated_column)) }); let line_count = if mappings.is_empty() { 0 } else { max_line as usize + 1 }; @@ -335,11 +333,7 @@ impl SectionMergeState { fn merge_scopes_and_ranges(&mut self) { let pending_scopes = std::mem::take(&mut self.pending_scopes); - merge_section_scopes_and_ranges( - pending_scopes, - &mut self.all_scopes, - &mut self.all_ranges, - ); + merge_section_scopes_and_ranges(pending_scopes, &mut self.all_scopes, &mut self.all_ranges); } } @@ -376,8 +370,7 @@ fn original_position_lub_index(line_mappings: &[Mapping], column: u32) -> Option // Mirrors `@jridgewell/trace-mapping`'s LUB = latest-equal tie-break. Ok(i) => { let mut idx = i; - while idx + 1 < line_mappings.len() - && line_mappings[idx + 1].generated_column == column + while idx + 1 < line_mappings.len() && line_mappings[idx + 1].generated_column == column { idx += 1; } @@ -1196,10 +1189,7 @@ impl SourceMap { if mapping.source != source_idx || mapping.original_line != line { return None; } - Some(GeneratedLocation { - line: mapping.generated_line, - column: mapping.generated_column, - }) + Some(GeneratedLocation { line: mapping.generated_line, column: mapping.generated_column }) } /// Find all generated positions for an original source position.