Skip to content

Commit bd4210f

Browse files
Boshenclaudeautofix-ci[bot]
authored
feat: add benchmark for package.json deserialization (#698)
## Summary - Added comprehensive benchmark suite for `PackageJson::parse()` to measure deserialization performance - Made `PackageJson::parse()` public API to enable benchmarking from outside the crate - Benchmarks cover various package.json complexity levels from simple to complex real-world files ## Benchmark Results The new benchmarks test different package.json sizes and parsing scenarios: | Benchmark | Time | Description | |-----------|------|-------------| | Small | ~323ns | Basic name/version fields only | | Medium | ~1.4µs | Includes main, exports, imports, browser fields | | Large | ~3.5µs | Complex nested exports/imports with dependencies | | Real Complex | ~2.3µs | Actual package.json from fixtures | | Batch (4 files) | ~7.7µs | Sequential parsing of 4 different files | | Parallel Batch | ~27.5µs | Parallel parsing using rayon | ## Motivation Package.json parsing is a critical operation in the resolver that happens frequently. Having benchmarks helps: - Track performance regressions - Optimize the parsing logic - Understand the performance characteristics of different package.json structures ## Test Plan - [x] Benchmarks compile and run successfully - [x] `cargo bench package_json_deserialization` produces consistent results - [x] No breaking changes to existing code (only made one internal function public) 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 79d5752 commit bd4210f

File tree

2 files changed

+142
-6
lines changed

2 files changed

+142
-6
lines changed

benches/resolver.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{
55
};
66

77
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
8+
use oxc_resolver::PackageJson;
89
use rayon::prelude::*;
910

1011
fn data() -> Vec<(PathBuf, &'static str)> {
@@ -199,5 +200,142 @@ fn bench_resolver(c: &mut Criterion) {
199200
);
200201
}
201202

202-
criterion_group!(resolver, bench_resolver);
203+
fn bench_package_json_deserialization(c: &mut Criterion) {
204+
let mut group = c.benchmark_group("package_json_deserialization");
205+
206+
// Prepare different sizes of package.json content
207+
let small_json = r#"{
208+
"name": "test-package",
209+
"version": "1.0.0"
210+
}"#;
211+
212+
let medium_json = r##"{
213+
"name": "test-package",
214+
"version": "1.0.0",
215+
"main": "./lib/index.js",
216+
"type": "module",
217+
"exports": {
218+
".": "./lib/index.js",
219+
"./feature": "./lib/feature.js"
220+
},
221+
"imports": {
222+
"#internal": "./src/internal.js"
223+
},
224+
"browser": {
225+
"./lib/node.js": "./lib/browser.js"
226+
},
227+
"sideEffects": false
228+
}"##;
229+
230+
let large_json = r##"{
231+
"name": "test-package",
232+
"version": "1.0.0",
233+
"main": "./lib/index.js",
234+
"type": "module",
235+
"exports": {
236+
".": {
237+
"import": "./lib/index.mjs",
238+
"require": "./lib/index.cjs",
239+
"browser": "./lib/browser.js"
240+
},
241+
"./feature": {
242+
"import": "./lib/feature.mjs",
243+
"require": "./lib/feature.cjs"
244+
},
245+
"./utils": "./lib/utils.js",
246+
"./internal/*": "./lib/internal/*.js"
247+
},
248+
"imports": {
249+
"#internal": "./src/internal.js",
250+
"#utils/*": "./src/utils/*.js"
251+
},
252+
"browser": {
253+
"./lib/node.js": "./lib/browser.js",
254+
"module-a": "./browser/module-a.js",
255+
"module-b": "module-c",
256+
"./lib/replaced.js": "./lib/browser"
257+
},
258+
"sideEffects": ["*.css", "*.scss"],
259+
"dependencies": {
260+
"lodash": "^4.17.21",
261+
"react": "^18.0.0",
262+
"express": "^4.18.0"
263+
},
264+
"devDependencies": {
265+
"typescript": "^5.0.0",
266+
"eslint": "^8.0.0",
267+
"jest": "^29.0.0"
268+
},
269+
"scripts": {
270+
"test": "jest",
271+
"build": "tsc",
272+
"lint": "eslint src"
273+
}
274+
}"##;
275+
276+
// Load real complex package.json from fixtures
277+
let complex_json_path = env::current_dir()
278+
.unwrap()
279+
.join("fixtures/enhanced_resolve/test/fixtures/browser-module/package.json");
280+
let complex_json =
281+
fs::read_to_string(&complex_json_path).expect("Failed to read complex package.json");
282+
283+
let test_path = PathBuf::from("/test/package.json");
284+
let test_realpath = test_path.clone();
285+
286+
group.bench_function("small", |b| {
287+
b.iter(|| {
288+
PackageJson::parse(test_path.clone(), test_realpath.clone(), small_json)
289+
.expect("Failed to parse small JSON");
290+
});
291+
});
292+
293+
group.bench_function("medium", |b| {
294+
b.iter(|| {
295+
PackageJson::parse(test_path.clone(), test_realpath.clone(), medium_json)
296+
.expect("Failed to parse medium JSON");
297+
});
298+
});
299+
300+
group.bench_function("large", |b| {
301+
b.iter(|| {
302+
PackageJson::parse(test_path.clone(), test_realpath.clone(), large_json)
303+
.expect("Failed to parse large JSON");
304+
});
305+
});
306+
307+
group.bench_function("complex_real", |b| {
308+
b.iter(|| {
309+
PackageJson::parse(test_path.clone(), test_realpath.clone(), &complex_json)
310+
.expect("Failed to parse complex JSON");
311+
});
312+
});
313+
314+
// Benchmark batch parsing (simulating resolver cache warming)
315+
let package_jsons = vec![small_json, medium_json, large_json, &complex_json];
316+
group.bench_function("batch_4_files", |b| {
317+
b.iter(|| {
318+
for (i, json) in package_jsons.iter().enumerate() {
319+
let path = PathBuf::from(format!("/test/package{i}.json"));
320+
PackageJson::parse(path.clone(), path, json)
321+
.expect("Failed to parse JSON in batch");
322+
}
323+
});
324+
});
325+
326+
// Benchmark parallel parsing
327+
group.bench_function("parallel_batch_4_files", |b| {
328+
b.iter(|| {
329+
package_jsons.par_iter().enumerate().for_each(|(i, json)| {
330+
let path = PathBuf::from(format!("/test/package{i}.json"));
331+
PackageJson::parse(path.clone(), path, json)
332+
.expect("Failed to parse JSON in parallel");
333+
});
334+
});
335+
});
336+
337+
group.finish();
338+
}
339+
340+
criterion_group!(resolver, bench_resolver, bench_package_json_deserialization);
203341
criterion_main!(resolver);

src/package_json.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,11 @@ impl PackageJson {
203203
Ok(None)
204204
}
205205

206+
/// Parse a package.json file from JSON string
207+
///
206208
/// # Panics
207209
/// # Errors
208-
pub(crate) fn parse(
209-
path: PathBuf,
210-
realpath: PathBuf,
211-
json: &str,
212-
) -> Result<Self, serde_json::Error> {
210+
pub fn parse(path: PathBuf, realpath: PathBuf, json: &str) -> Result<Self, serde_json::Error> {
213211
let json = json.trim_start_matches("\u{feff}"); // strip bom
214212
let mut raw_json: JSONValue = serde_json::from_str(json)?;
215213
let mut package_json = Self::default();

0 commit comments

Comments
 (0)