Skip to content

Commit 93c9b26

Browse files
committed
The analyze and fix CLI subcommands no longer build the cross-file
reference index
1 parent b4cb61c commit 93c9b26

5 files changed

Lines changed: 29 additions & 25 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5555
- **Repeated class lookups are remembered instead of searched again.** Answering "which class does this name refer to?" is the single most frequent thing PHPantom does: analysing a large Laravel project asks it millions of times, over only a few thousand distinct classes. Every question used to hash the name and take a read lock on two shared indexes, so with one worker per CPU core the workers spent much of their time queueing behind each other instead of analysing. Each worker now remembers the answers it has already looked up and drops them the moment the class indexes change, so a repeated question costs a single pointer comparison. Whole-project analysis is 8-12% faster on large Laravel projects and uses up to a third less CPU, with identical results and no measurable change in memory use. Hover, completion, and go-to-definition resolve names through the same path and get the same saving.
5656
- **Vendor package scanning no longer reads every file twice.** Startup used to scan each vendor file once to find its classes, functions, and constants, then read and scan it again just to classify which package it came from for completion ranking. Each file's package is now known before it is scanned, so both are done in a single parallel pass, roughly halving the I/O and CPU cost of the vendor scan.
5757
- **Whole-workspace class pre-resolution now spreads across cores.** Resolving every known class in dependency order after indexing ran on a single core, leaving a single-threaded pause between the parallel index and the parallel diagnostic pass that grew with the number of classes in the project. That work is now shared across a pool of workers, which cuts the phase to roughly a quarter of its previous time on large Laravel projects and takes close to 20% off whole-project analysis on the largest of them. The editor's post-startup pre-resolution goes through the same path, so a large project becomes fully warm sooner. Results are unchanged.
58+
- **The `analyze` and `fix` CLI subcommands no longer build the cross-file reference index.** That index only serves Find References, Rename, and reference-count inlay hints, none of which the CLI subcommands ever query, so every file they parsed was still paying to populate it. Skipping the build removes that wasted CPU and short-lived allocation from whole-project `analyze` and `fix` runs; the editor's LSP session is unaffected.
5859

5960
### Removed
6061

docs/todo.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ unlikely to move the needle for most users.
214214
| P21 | [Offset-shifting for cached diagnostics on partial edits](todo/performance.md#p21-offset-shifting-for-cached-diagnostics-on-partial-edits) | Medium | Medium |
215215
| P23 | [`workspace/symbol` lowercases every symbol name per request](todo/performance.md#p23-workspacesymbol-allocates-a-lowercase-copy-of-every-symbol-name-per-request) | Low-Medium | Low |
216216
| P28 | [`process_assert_narrowing` clones the scope per variable for every statement](todo/performance.md#p28-process_assert_narrowing-clones-the-top-level-scope-once-per-variable-for-every-statement) | Low-Medium | Low |
217-
| P42 | [Reference index is built during the headless `analyze` pipeline but never queried there](todo/performance.md#p42-reference-index-is-built-during-the-headless-analyze-pipeline-but-never-queried-there) | Low-Medium | Low |
218217
| | **[Indexing](todo/indexing.md)** | | |
219218
| X3 | Completion item detail on demand (`completionItem/resolve`) | Medium | Medium |
220219
| X7 | [Recency tracking](todo/indexing.md#x7-recency-tracking) | Medium | Medium |

docs/todo/performance.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,28 +1049,6 @@ real ceiling than the hashing was.
10491049

10501050
---
10511051

1052-
## P42. Reference index is built during the headless `analyze` pipeline but never queried there
1053-
1054-
**Impact: Low-Medium · Effort: Low**
1055-
1056-
`reindex_references_for_symbol_maps_batch` (`src/reference_index.rs`)
1057-
runs on every `update_ast`, including from the parallel index workers
1058-
in `src/analyse/run.rs` that back the headless `analyze` CLI
1059-
subcommand. That pipeline never calls
1060-
`reference_candidate_uris_for_keys` or any other reader of
1061-
`reference_index` (it has no find-references, rename, or inlay-hints
1062-
request to serve), so every span the workers walk to build index
1063-
entries there is wasted CPU and short-lived allocation.
1064-
1065-
Skip the build in that mode: add a per-`Backend` flag (not a
1066-
process-global static or thread-local, so test isolation between
1067-
`Backend` instances is preserved) that `Backend::new_headless` sets
1068-
and `reindex_references_for_symbol_maps_batch` checks before doing any
1069-
work, so the skip applies uniformly to every caller of `update_ast` in
1070-
headless mode rather than special-casing the `analyze` call site.
1071-
1072-
---
1073-
10741052
## P43. `init_single_project` is the longest single-threaded stretch of a run
10751053

10761054
**Impact: Medium-High · Effort: Medium**

src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,13 @@ pub struct Backend {
446446
/// candidate files, then run their existing semantic checks for aliases,
447447
/// inheritance, Laravel declarations, and `self/static/parent`.
448448
pub(crate) reference_index: reference_index::ReferenceIndex,
449+
/// Skip building [`reference_index`] from `update_ast`.
450+
///
451+
/// Set by [`Backend::new_headless`] for the `analyze`/`fix` CLI
452+
/// subcommands, which parse every file but never issue a
453+
/// find-references, rename, or inlay-hints request, so populating
454+
/// the index would be pure wasted CPU and short-lived allocation.
455+
pub(crate) skip_reference_index: bool,
449456
/// Per-file parse errors from the Mago parser.
450457
///
451458
/// Each entry is `(message, start_byte_offset, end_byte_offset)`.
@@ -849,6 +856,7 @@ impl Backend {
849856
open_files: Arc::new(RwLock::new(HashMap::new())),
850857
symbol_maps: Arc::new(RwLock::new(HashMap::new())),
851858
reference_index: reference_index::new_reference_index(),
859+
skip_reference_index: false,
852860
symbols: SymbolIndex::new(),
853861
workspace: WorkspaceEnv::new(),
854862
parse_errors: Arc::new(RwLock::new(HashMap::new())),
@@ -936,6 +944,7 @@ impl Backend {
936944
open_files: Arc::new(RwLock::new(HashMap::new())),
937945
symbol_maps: Arc::new(RwLock::new(HashMap::new())),
938946
reference_index: reference_index::new_reference_index(),
947+
skip_reference_index: false,
939948
symbols: SymbolIndex::new(),
940949
workspace: WorkspaceEnv::new(),
941950
parse_errors: Arc::new(RwLock::new(HashMap::new())),
@@ -1021,7 +1030,10 @@ impl Backend {
10211030
/// where there is no LSP client but the backend still needs access to
10221031
/// the PHP standard library stubs.
10231032
pub fn new_headless() -> Self {
1024-
Self::defaults()
1033+
Self {
1034+
skip_reference_index: true,
1035+
..Self::defaults()
1036+
}
10251037
}
10261038

10271039
/// Create a `Backend` without an LSP client (for unit / integration tests).
@@ -1556,6 +1568,7 @@ impl Backend {
15561568
open_files: Arc::clone(&self.open_files),
15571569
symbol_maps: Arc::clone(&self.symbol_maps),
15581570
reference_index: Arc::clone(&self.reference_index),
1571+
skip_reference_index: self.skip_reference_index,
15591572
symbols: self.symbols.clone(),
15601573
parse_errors: Arc::clone(&self.parse_errors),
15611574
did_change_parse_locks: Arc::clone(&self.did_change_parse_locks),

src/reference_index.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl Backend {
125125
&self,
126126
items: Vec<(String, Arc<SymbolMap>)>,
127127
) {
128-
if items.is_empty() {
128+
if items.is_empty() || self.skip_reference_index {
129129
return;
130130
}
131131

@@ -498,6 +498,19 @@ mod tests {
498498
assert!(backend.reference_index.read().is_empty());
499499
}
500500

501+
#[test]
502+
fn headless_backend_skips_reference_indexing() {
503+
let backend = Backend::new_headless();
504+
let uri = "file:///project/src/Foo.php".to_string();
505+
506+
backend.reindex_references_for_symbol_maps_batch(vec![(
507+
uri,
508+
class_declaration_symbol_map("App\\Foo"),
509+
)]);
510+
511+
assert!(backend.reference_index.read().is_empty());
512+
}
513+
501514
#[test]
502515
fn batch_reindex_skips_vendor_and_stub_uris() {
503516
let dir = tempfile::tempdir().expect("temp dir");

0 commit comments

Comments
 (0)