Skip to content
Open
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
16 changes: 16 additions & 0 deletions .github/workflows/corpus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,19 @@ jobs:
run: cargo test --release --locked -p zerosyntax-analysis --test corpus -- --ignored --nocapture
- name: Corpus incremental equivalence
run: cargo test --release --locked -p zerosyntax-analysis --test incremental corpus_edit_sequences_match_full_parse -- --ignored --nocapture
- name: Corpus performance benchmark
run: cargo bench --locked -p zerosyntax-analysis --bench analyze -- corpus
- name: Real-server corpus latency
run: |
cargo build --locked --release -p zerosyntax-server
python3 crates/server/tests/typing_latency.py \
target/release/zerosyntax-lsp \
corpus/GeneralsGamePatch2/GeneralsZH/Data/INI/ParticleSystem.ini \
--workspace-root corpus/GeneralsGamePatch2/GeneralsZH/Data/INI \
> "$RUNNER_TEMP/server-corpus.json"
- name: Check absolute performance ceilings
if: always()
run: |
python3 scripts/check-performance.py \
--head "$RUNNER_TEMP/server-corpus.json" \
--summary "$GITHUB_STEP_SUMMARY"
64 changes: 64 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Performance

on:
pull_request:
branches: [dev, prod]

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
performance:
runs-on: ubuntu-latest
steps:
- name: Check out PR head tools
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Preserve head measurement tools
run: |
cp crates/server/tests/e2e.py "$RUNNER_TEMP/e2e.py"
cp crates/server/tests/typing_latency.py "$RUNNER_TEMP/typing_latency.py"
- uses: Swatinem/rust-cache@v2

- name: Check out base
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
- name: Benchmark base
run: |
cargo bench --locked -p zerosyntax-syntax --bench parse -- 50000 --save-baseline pr-base
cargo bench --locked -p zerosyntax-analysis --bench analyze -- 50000 --save-baseline pr-base
- name: Measure base server
run: |
cargo build --locked --release -p zerosyntax-server
python3 "$RUNNER_TEMP/typing_latency.py" target/release/zerosyntax-lsp > "$RUNNER_TEMP/server-base.json"

- name: Check out head
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
clean: false
- name: Benchmark head
run: |
cargo bench --locked -p zerosyntax-syntax --bench parse -- 50000 --baseline pr-base
cargo bench --locked -p zerosyntax-analysis --bench analyze -- 50000 --baseline pr-base
- name: Measure head server
run: |
cargo build --locked --release -p zerosyntax-server
python3 "$RUNNER_TEMP/typing_latency.py" target/release/zerosyntax-lsp > "$RUNNER_TEMP/server-head.json"

- name: Check performance
if: always()
run: |
python3 scripts/check-performance.py \
--criterion target/criterion \
--base "$RUNNER_TEMP/server-base.json" \
--head "$RUNNER_TEMP/server-head.json" \
--summary "$GITHUB_STEP_SUMMARY"
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ cargo test --release -p zerosyntax-analysis --test corpus -- --ignored --nocaptu

On Windows, use `pwsh scripts/fetch-corpus.ps1`.

## Performance

Run the existing synthetic benchmarks and real-server driver before changing a
hot path:

```sh
cargo bench -p zerosyntax-syntax --bench parse
cargo bench -p zerosyntax-analysis --bench analyze
cargo build --release -p zerosyntax-server
python crates/server/tests/typing_latency.py target/release/zerosyntax-lsp
```

Pull requests warn when a probe regresses by 20% and fail at 50%; loose absolute
latency ceilings catch emergency-level slowdowns. Profile a failing probe before
weakening its threshold.

## Pull requests

- Keep changes focused. Split unrelated fixes into separate PRs.
Expand Down
67 changes: 66 additions & 1 deletion crates/analysis/benches/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use std::hint::black_box;

use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use zerosyntax_analysis::diagnostics::DiagnosticsCache;
use zerosyntax_analysis::{diagnostics, index, semantic, Analyzer};
use zerosyntax_analysis::{
completion, diagnostics, index, semantic, Analyzer, Span, WorkspaceIndex,
};
use zerosyntax_syntax::{Edit, Strategy};

/// Same shape as the syntax-crate generator, but kept schema-conformant so the
Expand Down Expand Up @@ -98,6 +100,69 @@ fn bench_analyze(c: &mut Criterion) {
&parse,
|b, parse| b.iter(|| black_box(index::definitions_in(&analyzer, parse, "bench.ini"))),
);
if lines == 50_000 {
group.bench_with_input(
BenchmarkId::new("index_refresh", lines),
&parse,
|b, parse| {
b.iter(|| {
let file = "bench.ini";
let mut index = WorkspaceIndex::new();
index.set_file(file, index::definitions_in(&analyzer, parse, file));
index.set_file_refs(file, index::references_in(&analyzer, parse));
index.set_file_tags(file, index::module_tags_in(&analyzer, parse));
index.set_file_object_models(
file,
index::object_models_in(&analyzer, parse),
);
index.set_file_object_parents(file, index::object_parents_in(parse));
black_box(index)
})
},
);

let mut completion_src = src.clone();
completion_src.push_str("Weapon CompletionBench\n ProjectileObject = \nEnd\n");
let completion_offset = (completion_src.len() - "\nEnd\n".len()) as u32;
let completion_parse = analyzer.parse(&completion_src);
let mut completion_index = WorkspaceIndex::new();
completion_index.set_file(
"bench.ini",
index::definitions_in(&analyzer, &completion_parse, "bench.ini"),
);
assert!(completion::complete(
&analyzer,
&completion_parse,
completion_offset,
Some(&completion_index),
Some("bench.ini"),
)
.iter()
.any(|item| item.label == "GenBenchTank1"));
group.bench_function(BenchmarkId::new("completion_reference", lines), |b| {
b.iter(|| {
black_box(completion::complete(
&analyzer,
&completion_parse,
completion_offset,
Some(&completion_index),
Some("bench.ini"),
))
})
});

let start = (src.len() / 2) as u32;
let viewport = Span::new(start, (start + 2_000).min(src.len() as u32));
group.bench_with_input(
BenchmarkId::new("semantic_tokens_range", lines),
&parse,
|b, parse| {
b.iter(|| {
black_box(semantic::semantic_tokens_range(&analyzer, parse, viewport))
})
},
);
}
// The full keystroke path as the server ran it pre-Phase-3:
// full parse + full diagnose.
group.bench_with_input(BenchmarkId::new("keystroke", lines), &src, |b, src| {
Expand Down
Loading
Loading