Skip to content

Commit e2e0889

Browse files
authored
[red-knot] Add very basic benchmark (#12182)
1 parent 497fd4c commit e2e0889

File tree

7 files changed

+197
-3
lines changed

7 files changed

+197
-3
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ ruff_source_file = { path = "crates/ruff_source_file" }
3535
ruff_text_size = { path = "crates/ruff_text_size" }
3636
ruff_workspace = { path = "crates/ruff_workspace" }
3737

38+
red_knot = { path = "crates/red_knot" }
3839
red_knot_module_resolver = { path = "crates/red_knot_module_resolver" }
3940
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
4041

crates/red_knot/src/program/check.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@ impl Program {
1111
self.with_db(|db| {
1212
let mut result = Vec::new();
1313
for open_file in db.workspace.open_files() {
14-
result.extend_from_slice(&db.check_file(open_file));
14+
result.extend_from_slice(&db.check_file_impl(open_file));
1515
}
1616

1717
result
1818
})
1919
}
2020

2121
#[tracing::instrument(level = "debug", skip(self))]
22-
fn check_file(&self, file: VfsFile) -> Diagnostics {
22+
pub fn check_file(&self, file: VfsFile) -> Result<Diagnostics, Cancelled> {
23+
self.with_db(|db| db.check_file_impl(file))
24+
}
25+
26+
fn check_file_impl(&self, file: VfsFile) -> Diagnostics {
2327
let mut diagnostics = Vec::new();
2428
diagnostics.extend_from_slice(lint_syntax(self, file));
2529
diagnostics.extend_from_slice(lint_semantic(self, file));

crates/ruff_benchmark/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ harness = false
3131
name = "formatter"
3232
harness = false
3333

34+
[[bench]]
35+
name = "red_knot"
36+
harness = false
37+
3438
[dependencies]
3539
once_cell = { workspace = true }
3640
serde = { workspace = true }
@@ -41,11 +45,14 @@ criterion = { workspace = true, default-features = false }
4145
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
4246

4347
[dev-dependencies]
48+
ruff_db = { workspace = true }
4449
ruff_linter = { workspace = true }
4550
ruff_python_ast = { workspace = true }
4651
ruff_python_formatter = { workspace = true }
4752
ruff_python_parser = { workspace = true }
4853
ruff_python_trivia = { workspace = true }
54+
red_knot = { workspace = true }
55+
red_knot_module_resolver = { workspace = true }
4956

5057
[lints]
5158
workspace = true
+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#![allow(clippy::disallowed_names)]
2+
3+
use red_knot::program::Program;
4+
use red_knot::Workspace;
5+
use red_knot_module_resolver::{set_module_resolution_settings, ModuleResolutionSettings};
6+
use ruff_benchmark::criterion::{
7+
criterion_group, criterion_main, BatchSize, Criterion, Throughput,
8+
};
9+
use ruff_db::file_system::{FileSystemPath, MemoryFileSystem};
10+
use ruff_db::parsed::parsed_module;
11+
use ruff_db::vfs::{system_path_to_file, VfsFile};
12+
use ruff_db::Upcast;
13+
14+
static FOO_CODE: &str = r#"
15+
import typing
16+
17+
from bar import Bar
18+
19+
class Foo(Bar):
20+
def foo() -> str:
21+
return "foo"
22+
23+
@typing.override
24+
def bar() -> str:
25+
return "foo_bar"
26+
"#;
27+
28+
static BAR_CODE: &str = r#"
29+
class Bar:
30+
def bar() -> str:
31+
return "bar"
32+
33+
def random(arg: int) -> int:
34+
if arg == 1:
35+
return 48472783
36+
if arg < 10:
37+
return 20
38+
return 36673
39+
"#;
40+
41+
static TYPING_CODE: &str = r#"
42+
def override(): ...
43+
"#;
44+
45+
struct Case {
46+
program: Program,
47+
fs: MemoryFileSystem,
48+
foo: VfsFile,
49+
bar: VfsFile,
50+
typing: VfsFile,
51+
}
52+
53+
fn setup_case() -> Case {
54+
let fs = MemoryFileSystem::new();
55+
let foo_path = FileSystemPath::new("/src/foo.py");
56+
let bar_path = FileSystemPath::new("/src/bar.py");
57+
let typing_path = FileSystemPath::new("/src/typing.pyi");
58+
fs.write_files([
59+
(foo_path, FOO_CODE),
60+
(bar_path, BAR_CODE),
61+
(typing_path, TYPING_CODE),
62+
])
63+
.unwrap();
64+
65+
let workspace_root = FileSystemPath::new("/src");
66+
let workspace = Workspace::new(workspace_root.to_path_buf());
67+
68+
let mut program = Program::new(workspace, fs.clone());
69+
let foo = system_path_to_file(&program, foo_path).unwrap();
70+
71+
set_module_resolution_settings(
72+
&mut program,
73+
ModuleResolutionSettings {
74+
extra_paths: vec![],
75+
workspace_root: workspace_root.to_path_buf(),
76+
site_packages: None,
77+
custom_typeshed: None,
78+
},
79+
);
80+
81+
program.workspace_mut().open_file(foo);
82+
83+
let bar = system_path_to_file(&program, bar_path).unwrap();
84+
let typing = system_path_to_file(&program, typing_path).unwrap();
85+
86+
Case {
87+
program,
88+
fs,
89+
foo,
90+
bar,
91+
typing,
92+
}
93+
}
94+
95+
fn benchmark_without_parse(criterion: &mut Criterion) {
96+
let mut group = criterion.benchmark_group("red_knot/check_file");
97+
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));
98+
99+
group.bench_function("red_knot_check_file[without_parse]", |b| {
100+
b.iter_batched(
101+
|| {
102+
let case = setup_case();
103+
// Pre-parse the module to only measure the semantic time.
104+
parsed_module(case.program.upcast(), case.foo);
105+
parsed_module(case.program.upcast(), case.bar);
106+
parsed_module(case.program.upcast(), case.typing);
107+
case
108+
},
109+
|case| {
110+
let Case { program, foo, .. } = case;
111+
let result = program.check_file(foo).unwrap();
112+
113+
assert_eq!(result.as_slice(), [] as [String; 0]);
114+
},
115+
BatchSize::SmallInput,
116+
);
117+
});
118+
119+
group.finish();
120+
}
121+
122+
fn benchmark_incremental(criterion: &mut Criterion) {
123+
let mut group = criterion.benchmark_group("red_knot/check_file");
124+
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));
125+
126+
group.bench_function("red_knot_check_file[incremental]", |b| {
127+
b.iter_batched(
128+
|| {
129+
let mut case = setup_case();
130+
case.program.check_file(case.foo).unwrap();
131+
132+
case.fs
133+
.write_file(
134+
FileSystemPath::new("/src/foo.py"),
135+
format!("{BAR_CODE}\n# A comment\n"),
136+
)
137+
.unwrap();
138+
139+
case.bar.touch(&mut case.program);
140+
case
141+
},
142+
|case| {
143+
let Case { program, foo, .. } = case;
144+
let result = program.check_file(foo).unwrap();
145+
146+
assert_eq!(result.as_slice(), [] as [String; 0]);
147+
},
148+
BatchSize::SmallInput,
149+
);
150+
});
151+
152+
group.finish();
153+
}
154+
155+
fn benchmark_cold(criterion: &mut Criterion) {
156+
let mut group = criterion.benchmark_group("red_knot/check_file");
157+
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));
158+
159+
group.bench_function("red_knot_check_file[cold]", |b| {
160+
b.iter_batched(
161+
setup_case,
162+
|case| {
163+
let Case { program, foo, .. } = case;
164+
let result = program.check_file(foo).unwrap();
165+
166+
assert_eq!(result.as_slice(), [] as [String; 0]);
167+
},
168+
BatchSize::SmallInput,
169+
);
170+
});
171+
172+
group.finish();
173+
}
174+
175+
criterion_group!(cold, benchmark_without_parse);
176+
criterion_group!(without_parse, benchmark_cold);
177+
criterion_group!(incremental, benchmark_incremental);
178+
criterion_main!(without_parse, cold, incremental);

crates/ruff_db/src/file_system/memory.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result}
1919
/// Use a tempdir with the real file system to test these advanced file system features and complex file system behavior.
2020
///
2121
/// Only intended for testing purposes.
22+
#[derive(Clone)]
2223
pub struct MemoryFileSystem {
2324
inner: Arc<MemoryFileSystemInner>,
2425
}

crates/ruff_db/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::parsed::parsed_module;
88
use crate::source::{line_index, source_text};
99
use crate::vfs::{Vfs, VfsFile};
1010

11-
mod file_revision;
11+
pub mod file_revision;
1212
pub mod file_system;
1313
pub mod parsed;
1414
pub mod source;

0 commit comments

Comments
 (0)