Skip to content

Commit 17d4db0

Browse files
committed
Auto merge of #10754 - Muscraft:benchsuite, r=epage
Add a benchmark for workspace initialization It [was suggested](#10736 (comment)) that a benchmark for workspace initialization should be added. This was suggested because there were issues with the performance of [workspace inheritance](#10747) as well as a general way to track the workspace initialization time across cargo changes ### Changes - Moved common functions out of `resolve.rs` to a shared `lib.rs` - Added a new struct to be used when creating a new benchmark - This was done because `env!("CARGO_TARGET_TMPDIR")` would fail to compile when put inside of the new `lib.rs` - Added a new workspace test for workspace inheritance - This new workspace does not have a repo that it was built from and if one needs to be made I can change that
2 parents 8d42b0e + f182411 commit 17d4db0

File tree

5 files changed

+243
-192
lines changed

5 files changed

+243
-192
lines changed

benches/benchsuite/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ description = "Benchmarking suite for Cargo."
1010

1111
[dependencies]
1212
cargo = { path = "../.." }
13+
cargo-test-support = { path = "../../crates/cargo-test-support" }
1314
# Consider removing html_reports in 0.4 and switching to `cargo criterion`.
1415
criterion = { version = "0.3.5", features = ["html_reports"] }
1516
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
1617
tar = { version = "0.4.38", default-features = false }
1718
url = "2.2.2"
1819

20+
[lib]
21+
bench = false
22+
1923
[[bench]]
2024
name = "resolve"
2125
harness = false
26+
27+
[[bench]]
28+
name = "workspace_initialization"
29+
harness = false

benches/benchsuite/benches/resolve.rs

+11-192
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,12 @@
1+
use benchsuite::fixtures;
12
use cargo::core::compiler::{CompileKind, RustcTargetData};
2-
use cargo::core::resolver::features::{CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets};
3-
use cargo::core::resolver::{HasDevUnits, ResolveBehavior};
3+
use cargo::core::resolver::features::{FeatureOpts, FeatureResolver};
4+
use cargo::core::resolver::{CliFeatures, ForceAllTargets, HasDevUnits, ResolveBehavior};
45
use cargo::core::{PackageIdSpec, Workspace};
56
use cargo::ops::WorkspaceResolve;
67
use cargo::Config;
78
use criterion::{criterion_group, criterion_main, Criterion};
8-
use std::fs;
9-
use std::path::{Path, PathBuf};
10-
use std::process::Command;
11-
use url::Url;
12-
13-
// This is an arbitrary commit that existed when I started. This helps
14-
// ensure consistent results. It can be updated if needed, but that can
15-
// make it harder to compare results with older versions of cargo.
16-
const CRATES_IO_COMMIT: &str = "85f7bfd61ea4fee08ec68c468762e886b2aebec6";
17-
18-
fn setup() {
19-
create_home();
20-
create_target_dir();
21-
clone_index();
22-
unpack_workspaces();
23-
}
24-
25-
fn root() -> PathBuf {
26-
let mut p = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
27-
p.push("bench");
28-
p
29-
}
30-
31-
fn target_dir() -> PathBuf {
32-
let mut p = root();
33-
p.push("target");
34-
p
35-
}
36-
37-
fn cargo_home() -> PathBuf {
38-
let mut p = root();
39-
p.push("chome");
40-
p
41-
}
42-
43-
fn index() -> PathBuf {
44-
let mut p = root();
45-
p.push("index");
46-
p
47-
}
48-
49-
fn workspaces_path() -> PathBuf {
50-
let mut p = root();
51-
p.push("workspaces");
52-
p
53-
}
54-
55-
fn registry_url() -> Url {
56-
Url::from_file_path(index()).unwrap()
57-
}
58-
59-
fn create_home() {
60-
let home = cargo_home();
61-
if !home.exists() {
62-
fs::create_dir_all(&home).unwrap();
63-
}
64-
fs::write(
65-
home.join("config.toml"),
66-
format!(
67-
r#"
68-
[source.crates-io]
69-
replace-with = 'local-snapshot'
70-
71-
[source.local-snapshot]
72-
registry = '{}'
73-
"#,
74-
registry_url()
75-
),
76-
)
77-
.unwrap();
78-
}
79-
80-
fn create_target_dir() {
81-
// This is necessary to ensure the .rustc_info.json file is written.
82-
// Otherwise it won't be written, and it is very expensive to create.
83-
if !target_dir().exists() {
84-
std::fs::create_dir_all(target_dir()).unwrap();
85-
}
86-
}
87-
88-
/// This clones crates.io at a specific point in time into tmp/index.
89-
fn clone_index() {
90-
let index = index();
91-
let maybe_git = |command: &str| {
92-
let status = Command::new("git")
93-
.current_dir(&index)
94-
.args(command.split_whitespace().collect::<Vec<_>>())
95-
.status()
96-
.expect("git should be installed");
97-
status.success()
98-
};
99-
let git = |command: &str| {
100-
if !maybe_git(command) {
101-
panic!("failed to run git command: {}", command);
102-
}
103-
};
104-
if index.exists() {
105-
if maybe_git(&format!(
106-
"rev-parse -q --verify {}^{{commit}}",
107-
CRATES_IO_COMMIT
108-
)) {
109-
// Already fetched.
110-
return;
111-
}
112-
} else {
113-
fs::create_dir_all(&index).unwrap();
114-
git("init --bare");
115-
git("remote add origin https://github.com/rust-lang/crates.io-index");
116-
}
117-
git(&format!("fetch origin {}", CRATES_IO_COMMIT));
118-
git("branch -f master FETCH_HEAD");
119-
}
120-
121-
/// This unpacks the compressed workspace skeletons into tmp/workspaces.
122-
fn unpack_workspaces() {
123-
let ws_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
124-
.parent()
125-
.unwrap()
126-
.join("workspaces");
127-
let archives = fs::read_dir(ws_dir)
128-
.unwrap()
129-
.map(|e| e.unwrap().path())
130-
.filter(|p| p.extension() == Some(std::ffi::OsStr::new("tgz")));
131-
for archive in archives {
132-
let name = archive.file_stem().unwrap();
133-
let f = fs::File::open(&archive).unwrap();
134-
let f = flate2::read::GzDecoder::new(f);
135-
let dest = workspaces_path().join(&name);
136-
if dest.exists() {
137-
fs::remove_dir_all(&dest).unwrap();
138-
}
139-
let mut archive = tar::Archive::new(f);
140-
archive.unpack(workspaces_path()).unwrap();
141-
}
142-
}
9+
use std::path::Path;
14310

14411
struct ResolveInfo<'cfg> {
14512
ws: Workspace<'cfg>,
@@ -152,36 +19,12 @@ struct ResolveInfo<'cfg> {
15219
ws_resolve: WorkspaceResolve<'cfg>,
15320
}
15421

155-
/// Vec of `(ws_name, ws_root)`.
156-
fn workspaces() -> Vec<(String, PathBuf)> {
157-
// CARGO_BENCH_WORKSPACES can be used to override, otherwise it just uses
158-
// the workspaces in the workspaces directory.
159-
let mut ps: Vec<_> = match std::env::var_os("CARGO_BENCH_WORKSPACES") {
160-
Some(s) => std::env::split_paths(&s).collect(),
161-
None => fs::read_dir(workspaces_path())
162-
.unwrap()
163-
.map(|e| e.unwrap().path())
164-
// These currently fail in most cases on Windows due to long
165-
// filenames in the git checkouts.
166-
.filter(|p| {
167-
!(cfg!(windows)
168-
&& matches!(p.file_name().unwrap().to_str().unwrap(), "servo" | "tikv"))
169-
})
170-
.collect(),
171-
};
172-
// Sort so it is consistent.
173-
ps.sort();
174-
ps.into_iter()
175-
.map(|p| (p.file_name().unwrap().to_str().unwrap().to_owned(), p))
176-
.collect()
177-
}
178-
17922
/// Helper for resolving a workspace. This will run the resolver once to
18023
/// download everything, and returns all the data structures that are used
18124
/// during resolution.
18225
fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> {
18326
let requested_kinds = [CompileKind::Host];
184-
let ws = cargo::core::Workspace::new(&ws_root.join("Cargo.toml"), config).unwrap();
27+
let ws = Workspace::new(&ws_root.join("Cargo.toml"), config).unwrap();
18528
let target_data = RustcTargetData::new(&ws, &requested_kinds).unwrap();
18629
let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap();
18730
let pkgs = cargo::ops::Packages::Default;
@@ -212,38 +55,14 @@ fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> {
21255
}
21356
}
21457

215-
/// Creates a new Config.
216-
///
217-
/// This is separate from `do_resolve` to deal with the ownership and lifetime.
218-
fn make_config(ws_root: &Path) -> Config {
219-
let shell = cargo::core::Shell::new();
220-
let mut config = cargo::util::Config::new(shell, ws_root.to_path_buf(), cargo_home());
221-
// Configure is needed to set the target_dir which is needed to write
222-
// the .rustc_info.json file which is very expensive.
223-
config
224-
.configure(
225-
0,
226-
false,
227-
None,
228-
false,
229-
false,
230-
false,
231-
&Some(target_dir()),
232-
&[],
233-
&[],
234-
)
235-
.unwrap();
236-
config
237-
}
238-
23958
/// Benchmark of the full `resolve_ws_with_opts` which runs the resolver
24059
/// twice, the feature resolver, and more. This is a major component of a
24160
/// regular cargo build.
24261
fn resolve_ws(c: &mut Criterion) {
243-
setup();
62+
let fixtures = fixtures!();
24463
let mut group = c.benchmark_group("resolve_ws");
245-
for (ws_name, ws_root) in workspaces() {
246-
let config = make_config(&ws_root);
64+
for (ws_name, ws_root) in fixtures.workspaces() {
65+
let config = fixtures.make_config(&ws_root);
24766
// The resolver info is initialized only once in a lazy fashion. This
24867
// allows criterion to skip this workspace if the user passes a filter
24968
// on the command-line (like `cargo bench -- resolve_ws/tikv`).
@@ -282,10 +101,10 @@ fn resolve_ws(c: &mut Criterion) {
282101

283102
/// Benchmark of the feature resolver.
284103
fn feature_resolver(c: &mut Criterion) {
285-
setup();
104+
let fixtures = fixtures!();
286105
let mut group = c.benchmark_group("feature_resolver");
287-
for (ws_name, ws_root) in workspaces() {
288-
let config = make_config(&ws_root);
106+
for (ws_name, ws_root) in fixtures.workspaces() {
107+
let config = fixtures.make_config(&ws_root);
289108
let mut lazy_info = None;
290109
group.bench_function(&ws_name, |b| {
291110
let ResolveInfo {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use benchsuite::fixtures;
2+
use cargo::core::Workspace;
3+
use criterion::{criterion_group, criterion_main, Criterion};
4+
5+
fn workspace_initialization(c: &mut Criterion) {
6+
let fixtures = fixtures!();
7+
let mut group = c.benchmark_group("workspace_initialization");
8+
for (ws_name, ws_root) in fixtures.workspaces() {
9+
let config = fixtures.make_config(&ws_root);
10+
// The resolver info is initialized only once in a lazy fashion. This
11+
// allows criterion to skip this workspace if the user passes a filter
12+
// on the command-line (like `cargo bench -- workspace_initialization/tikv`).
13+
group.bench_function(ws_name, |b| {
14+
b.iter(|| Workspace::new(&ws_root.join("Cargo.toml"), &config).unwrap())
15+
});
16+
}
17+
group.finish();
18+
}
19+
20+
// Criterion complains about the measurement time being too small, but the
21+
// measurement time doesn't seem important to me, what is more important is
22+
// the number of iterations which defaults to 100, which seems like a
23+
// reasonable default. Otherwise, the measurement time would need to be
24+
// changed per workspace. We wouldn't want to spend 60s on every workspace,
25+
// that would take too long and isn't necessary for the smaller workspaces.
26+
criterion_group!(benches, workspace_initialization);
27+
criterion_main!(benches);

0 commit comments

Comments
 (0)