diff --git a/Cargo.lock b/Cargo.lock index 93e6fcd5..aa72fe2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allsorts-azul" version = "0.17.2" @@ -134,6 +143,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -731,6 +746,12 @@ dependencies = [ "zip 8.6.0", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbc" version = "0.2.1" @@ -794,6 +815,33 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -999,6 +1047,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "crossbeam-deque" version = "0.8.7" @@ -1956,6 +2039,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2476,6 +2568,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "option-ext" version = "0.2.0" @@ -2516,6 +2614,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" @@ -2658,6 +2766,34 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.18.1" @@ -3000,7 +3136,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.14.0", "libc", "libfuzzer-sys", "log", @@ -3089,6 +3225,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" @@ -3275,6 +3423,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3771,6 +3928,7 @@ dependencies = [ name = "techscript_lsp" version = "2.0.0" dependencies = [ + "criterion", "serde", "serde_json", "techscript_ast", @@ -4038,6 +4196,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -4426,6 +4594,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4496,6 +4674,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -4542,6 +4730,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/tools/lsp/Cargo.toml b/tools/lsp/Cargo.toml index dfe0d447..9c857efc 100644 --- a/tools/lsp/Cargo.toml +++ b/tools/lsp/Cargo.toml @@ -20,3 +20,10 @@ tower-lsp = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { version = "1", features = ["full"] } + +[dev-dependencies] +criterion = "0.8.2" + +[[bench]] +name = "bench" +harness = false diff --git a/tools/lsp/benches/bench.rs b/tools/lsp/benches/bench.rs new file mode 100644 index 00000000..bac9cdd1 --- /dev/null +++ b/tools/lsp/benches/bench.rs @@ -0,0 +1,64 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +fn find_boundary_slow(line: &str, word: &str) -> usize { + let mut count = 0; + let word_len = word.len(); + let mut start_pos = 0; + while let Some(pos_in_line) = line[start_pos..].find(word) { + let actual_pos = start_pos + pos_in_line; + let char_before = if actual_pos > 0 { + line.chars().nth(actual_pos - 1) + } else { + None + }; + let char_after = line.chars().nth(actual_pos + word_len); + + let is_boundary_before = char_before.is_none_or(|c| !c.is_alphanumeric() && c != '_'); + let is_boundary_after = char_after.is_none_or(|c| !c.is_alphanumeric() && c != '_'); + + if is_boundary_before && is_boundary_after { + count += 1; + } + start_pos = actual_pos + word_len; + } + count +} + +fn find_boundary_fast(line: &str, word: &str) -> usize { + let mut count = 0; + let word_len = word.len(); + let mut start_pos = 0; + while let Some(pos_in_line) = line[start_pos..].find(word) { + let actual_pos = start_pos + pos_in_line; + let char_before = if actual_pos > 0 { + line[..actual_pos].chars().next_back() + } else { + None + }; + let char_after = line[actual_pos + word_len..].chars().next(); + + let is_boundary_before = char_before.is_none_or(|c| !c.is_alphanumeric() && c != '_'); + let is_boundary_after = char_after.is_none_or(|c| !c.is_alphanumeric() && c != '_'); + + if is_boundary_before && is_boundary_after { + count += 1; + } + start_pos = actual_pos + word_len; + } + count +} + +fn criterion_benchmark(c: &mut Criterion) { + let long_line = "let abc = 123; ".repeat(1000); + let word = "abc"; + + c.bench_function("slow", |b| { + b.iter(|| find_boundary_slow(std::hint::black_box(&long_line), std::hint::black_box(word))) + }); + c.bench_function("fast", |b| { + b.iter(|| find_boundary_fast(std::hint::black_box(&long_line), std::hint::black_box(word))) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/tools/lsp/src/lib.rs b/tools/lsp/src/lib.rs index 7ef4fad8..15caf177 100644 --- a/tools/lsp/src/lib.rs +++ b/tools/lsp/src/lib.rs @@ -752,11 +752,11 @@ impl LanguageServer for Backend { while let Some(pos_in_line) = line[start_pos..].find(&word) { let actual_pos = start_pos + pos_in_line; let char_before = if actual_pos > 0 { - line.chars().nth(actual_pos - 1) + line[..actual_pos].chars().next_back() } else { None }; - let char_after = line.chars().nth(actual_pos + word_len); + let char_after = line[actual_pos + word_len..].chars().next(); let is_boundary_before = char_before.map_or(true, |c| !c.is_alphanumeric() && c != '_'); @@ -803,11 +803,11 @@ impl LanguageServer for Backend { while let Some(pos_in_line) = line[start_pos..].find(&word) { let actual_pos = start_pos + pos_in_line; let char_before = if actual_pos > 0 { - line.chars().nth(actual_pos - 1) + line[..actual_pos].chars().next_back() } else { None }; - let char_after = line.chars().nth(actual_pos + word_len); + let char_after = line[actual_pos + word_len..].chars().next(); let is_boundary_before = char_before.map_or(true, |c| !c.is_alphanumeric() && c != '_');