Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Add very basic benchmark #12182

Merged
merged 3 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ ruff_source_file = { path = "crates/ruff_source_file" }
ruff_text_size = { path = "crates/ruff_text_size" }
ruff_workspace = { path = "crates/ruff_workspace" }

red_knot = { path = "crates/red_knot" }
red_knot_module_resolver = { path = "crates/red_knot_module_resolver" }
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }

Expand Down
8 changes: 6 additions & 2 deletions crates/red_knot/src/program/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ impl Program {
self.with_db(|db| {
let mut result = Vec::new();
for open_file in db.workspace.open_files() {
result.extend_from_slice(&db.check_file(open_file));
result.extend_from_slice(&db.check_file_impl(open_file));
}

result
})
}

#[tracing::instrument(level = "debug", skip(self))]
fn check_file(&self, file: VfsFile) -> Diagnostics {
pub fn check_file(&self, file: VfsFile) -> Result<Diagnostics, Cancelled> {
self.with_db(|db| db.check_file_impl(file))
}

fn check_file_impl(&self, file: VfsFile) -> Diagnostics {
let mut diagnostics = Vec::new();
diagnostics.extend_from_slice(lint_syntax(self, file));
diagnostics.extend_from_slice(lint_semantic(self, file));
Expand Down
7 changes: 7 additions & 0 deletions crates/ruff_benchmark/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ harness = false
name = "formatter"
harness = false

[[bench]]
name = "red_knot"
harness = false

[dependencies]
once_cell = { workspace = true }
serde = { workspace = true }
Expand All @@ -41,11 +45,14 @@ criterion = { workspace = true, default-features = false }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }

[dev-dependencies]
ruff_db = { workspace = true }
ruff_linter = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
red_knot = { workspace = true }
red_knot_module_resolver = { workspace = true }

[lints]
workspace = true
Expand Down
178 changes: 178 additions & 0 deletions crates/ruff_benchmark/benches/red_knot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#![allow(clippy::disallowed_names)]

use red_knot::program::Program;
use red_knot::Workspace;
use red_knot_module_resolver::{set_module_resolution_settings, ModuleResolutionSettings};
use ruff_benchmark::criterion::{
criterion_group, criterion_main, BatchSize, Criterion, Throughput,
};
use ruff_db::file_system::{FileSystemPath, MemoryFileSystem};
use ruff_db::parsed::parsed_module;
use ruff_db::vfs::{system_path_to_file, VfsFile};
use ruff_db::Upcast;

static FOO_CODE: &str = r#"
import typing

from bar import Bar

class Foo(Bar):
def foo() -> str:
return "foo"

@typing.override
def bar() -> str:
return "foo_bar"
"#;

static BAR_CODE: &str = r#"
class Bar:
def bar() -> str:
return "bar"

def random(arg: int) -> int:
if arg == 1:
return 48472783
if arg < 10:
return 20
return 36673
"#;

static TYPING_CODE: &str = r#"
def override(): ...
"#;

struct Case {
program: Program,
fs: MemoryFileSystem,
foo: VfsFile,
bar: VfsFile,
typing: VfsFile,
}

fn setup_case() -> Case {
let fs = MemoryFileSystem::new();
let foo_path = FileSystemPath::new("/src/foo.py");
let bar_path = FileSystemPath::new("/src/bar.py");
let typing_path = FileSystemPath::new("/src/typing.pyi");
fs.write_files([
(foo_path, FOO_CODE),
(bar_path, BAR_CODE),
(typing_path, TYPING_CODE),
])
.unwrap();

let workspace_root = FileSystemPath::new("/src");
let workspace = Workspace::new(workspace_root.to_path_buf());

let mut program = Program::new(workspace, fs.clone());
let foo = system_path_to_file(&program, foo_path).unwrap();

set_module_resolution_settings(
&mut program,
ModuleResolutionSettings {
extra_paths: vec![],
workspace_root: workspace_root.to_path_buf(),
site_packages: None,
custom_typeshed: None,
},
);

program.workspace_mut().open_file(foo);

let bar = system_path_to_file(&program, bar_path).unwrap();
let typing = system_path_to_file(&program, typing_path).unwrap();

Case {
program,
fs,
foo,
bar,
typing,
}
}

fn benchmark_without_parse(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("red_knot/check_file");
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));

group.bench_function("red_knot_check_file[without_parse]", |b| {
b.iter_batched(
|| {
let case = setup_case();
// Pre-parse the module to only measure the semantic time.
parsed_module(case.program.upcast(), case.foo);
parsed_module(case.program.upcast(), case.bar);
parsed_module(case.program.upcast(), case.typing);
case
},
|case| {
let Case { program, foo, .. } = case;
let result = program.check_file(foo).unwrap();

assert_eq!(result.as_slice(), [] as [String; 0]);
},
BatchSize::SmallInput,
);
});

group.finish();
}

fn benchmark_incremental(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("red_knot/check_file");
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));

group.bench_function("red_knot_check_file[incremental]", |b| {
b.iter_batched(
|| {
let mut case = setup_case();
case.program.check_file(case.foo).unwrap();

case.fs
.write_file(
FileSystemPath::new("/src/foo.py"),
format!("{BAR_CODE}\n# A comment\n"),
)
.unwrap();

case.bar.touch(&mut case.program);
case
},
|case| {
let Case { program, foo, .. } = case;
let result = program.check_file(foo).unwrap();

assert_eq!(result.as_slice(), [] as [String; 0]);
},
BatchSize::SmallInput,
);
});

group.finish();
}

fn benchmark_cold(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("red_knot/check_file");
group.throughput(Throughput::Bytes(FOO_CODE.len() as u64));

group.bench_function("red_knot_check_file[cold]", |b| {
b.iter_batched(
setup_case,
|case| {
let Case { program, foo, .. } = case;
let result = program.check_file(foo).unwrap();

assert_eq!(result.as_slice(), [] as [String; 0]);
},
BatchSize::SmallInput,
);
});

group.finish();
}

criterion_group!(cold, benchmark_without_parse);
criterion_group!(without_parse, benchmark_cold);
criterion_group!(incremental, benchmark_incremental);
criterion_main!(without_parse, cold, incremental);
1 change: 1 addition & 0 deletions crates/ruff_db/src/file_system/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result}
/// Use a tempdir with the real file system to test these advanced file system features and complex file system behavior.
///
/// Only intended for testing purposes.
#[derive(Clone)]
pub struct MemoryFileSystem {
inner: Arc<MemoryFileSystemInner>,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::parsed::parsed_module;
use crate::source::{line_index, source_text};
use crate::vfs::{Vfs, VfsFile};

mod file_revision;
pub mod file_revision;
pub mod file_system;
pub mod parsed;
pub mod source;
Expand Down
Loading