Skip to content

Commit 9bee942

Browse files
authored
[red-knot] add a large-union-of-string-literals benchmark (#17393)
## Summary Add a benchmark for a large-union case that currently has exponential blow-up in execution time. ## Test Plan `cargo bench --bench red_knot`
1 parent 17e5b61 commit 9bee942

File tree

1 file changed

+98
-17
lines changed

1 file changed

+98
-17
lines changed

crates/ruff_benchmark/benches/red_knot.rs

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use ruff_python_ast::PythonVersion;
2121
struct Case {
2222
db: ProjectDatabase,
2323
fs: MemoryFileSystem,
24-
re: File,
25-
re_path: SystemPathBuf,
24+
file: File,
25+
file_path: SystemPathBuf,
2626
}
2727

2828
// "https://raw.githubusercontent.com/python/cpython/8e8a4baf652f6e1cee7acde9d78c4b6154539748/Lib/tomllib";
@@ -59,7 +59,7 @@ type KeyDiagnosticFields = (
5959
Severity,
6060
);
6161

62-
static EXPECTED_DIAGNOSTICS: &[KeyDiagnosticFields] = &[(
62+
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[(
6363
DiagnosticId::lint("unused-ignore-comment"),
6464
Some("/src/tomllib/_parser.py"),
6565
Some(22299..22333),
@@ -71,7 +71,7 @@ fn tomllib_path(file: &TestFile) -> SystemPathBuf {
7171
SystemPathBuf::from("src").join(file.name())
7272
}
7373

74-
fn setup_case() -> Case {
74+
fn setup_tomllib_case() -> Case {
7575
let system = TestSystem::default();
7676
let fs = system.memory_file_system().clone();
7777

@@ -112,8 +112,8 @@ fn setup_case() -> Case {
112112
Case {
113113
db,
114114
fs,
115-
re,
116-
re_path,
115+
file: re,
116+
file_path: re_path,
117117
}
118118
}
119119

@@ -135,16 +135,19 @@ fn setup_rayon() {
135135

136136
fn benchmark_incremental(criterion: &mut Criterion) {
137137
fn setup() -> Case {
138-
let case = setup_case();
138+
let case = setup_tomllib_case();
139139

140140
let result: Vec<_> = case.db.check().unwrap();
141141

142-
assert_diagnostics(&case.db, &result);
142+
assert_diagnostics(&case.db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS);
143143

144144
case.fs
145145
.write_file_all(
146-
&case.re_path,
147-
format!("{}\n# A comment\n", source_text(&case.db, case.re).as_str()),
146+
&case.file_path,
147+
format!(
148+
"{}\n# A comment\n",
149+
source_text(&case.db, case.file).as_str()
150+
),
148151
)
149152
.unwrap();
150153

@@ -156,15 +159,15 @@ fn benchmark_incremental(criterion: &mut Criterion) {
156159

157160
db.apply_changes(
158161
vec![ChangeEvent::Changed {
159-
path: case.re_path.clone(),
162+
path: case.file_path.clone(),
160163
kind: ChangedKind::FileContent,
161164
}],
162165
None,
163166
);
164167

165168
let result = db.check().unwrap();
166169

167-
assert_eq!(result.len(), EXPECTED_DIAGNOSTICS.len());
170+
assert_eq!(result.len(), EXPECTED_TOMLLIB_DIAGNOSTICS.len());
168171
}
169172

170173
setup_rayon();
@@ -179,20 +182,20 @@ fn benchmark_cold(criterion: &mut Criterion) {
179182

180183
criterion.bench_function("red_knot_check_file[cold]", |b| {
181184
b.iter_batched_ref(
182-
setup_case,
185+
setup_tomllib_case,
183186
|case| {
184187
let Case { db, .. } = case;
185188
let result: Vec<_> = db.check().unwrap();
186189

187-
assert_diagnostics(db, &result);
190+
assert_diagnostics(db, &result, EXPECTED_TOMLLIB_DIAGNOSTICS);
188191
},
189192
BatchSize::SmallInput,
190193
);
191194
});
192195
}
193196

194197
#[track_caller]
195-
fn assert_diagnostics(db: &dyn Db, diagnostics: &[Diagnostic]) {
198+
fn assert_diagnostics(db: &dyn Db, diagnostics: &[Diagnostic], expected: &[KeyDiagnosticFields]) {
196199
let normalized: Vec<_> = diagnostics
197200
.iter()
198201
.map(|diagnostic| {
@@ -211,8 +214,86 @@ fn assert_diagnostics(db: &dyn Db, diagnostics: &[Diagnostic]) {
211214
)
212215
})
213216
.collect();
214-
assert_eq!(&normalized, EXPECTED_DIAGNOSTICS);
217+
assert_eq!(&normalized, expected);
218+
}
219+
220+
fn setup_micro_case(code: &str) -> Case {
221+
let system = TestSystem::default();
222+
let fs = system.memory_file_system().clone();
223+
224+
let file_path = "src/test.py";
225+
fs.write_file_all(
226+
SystemPathBuf::from(file_path),
227+
ruff_python_trivia::textwrap::dedent(code),
228+
)
229+
.unwrap();
230+
231+
let src_root = SystemPath::new("/src");
232+
let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap();
233+
metadata.apply_cli_options(Options {
234+
environment: Some(EnvironmentOptions {
235+
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
236+
..EnvironmentOptions::default()
237+
}),
238+
..Options::default()
239+
});
240+
241+
let mut db = ProjectDatabase::new(metadata, system).unwrap();
242+
let file = system_path_to_file(&db, SystemPathBuf::from(file_path)).unwrap();
243+
244+
db.project()
245+
.set_open_files(&mut db, FxHashSet::from_iter([file]));
246+
247+
let file_path = file.path(&db).as_system_path().unwrap().to_owned();
248+
249+
Case {
250+
db,
251+
fs,
252+
file,
253+
file_path,
254+
}
255+
}
256+
257+
fn benchmark_many_string_assignments(criterion: &mut Criterion) {
258+
setup_rayon();
259+
260+
criterion.bench_function("red_knot_micro[many_string_assignments]", |b| {
261+
b.iter_batched_ref(
262+
|| {
263+
setup_micro_case(
264+
r#"
265+
def f(x) -> str:
266+
s = ""
267+
if x.attr1:
268+
s += "attr1"
269+
if x.attr2:
270+
s += "attr2"
271+
if x.attr3:
272+
s += "attr3"
273+
if x.attr4:
274+
s += "attr4"
275+
if x.attr5:
276+
s += "attr5"
277+
if x.attr6:
278+
s += "attr6"
279+
if x.attr7:
280+
s += "attr7"
281+
if x.attr8:
282+
s += "attr8"
283+
return s
284+
"#,
285+
)
286+
},
287+
|case| {
288+
let Case { db, .. } = case;
289+
let result = db.check().unwrap();
290+
assert_eq!(result.len(), 0);
291+
},
292+
BatchSize::SmallInput,
293+
);
294+
});
215295
}
216296

217297
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
218-
criterion_main!(check_file);
298+
criterion_group!(micro, benchmark_many_string_assignments);
299+
criterion_main!(check_file, micro);

0 commit comments

Comments
 (0)