Skip to content

Commit 8c704fd

Browse files
committed
Faster project startup
1 parent ce58861 commit 8c704fd

8 files changed

Lines changed: 496 additions & 362 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5353
- **Faster whole-workspace class pre-resolution.** Resolving every known class in dependency order after indexing now spreads across multiple workers instead of running on a single one, substantially cutting the pause between indexing and diagnostics on large Laravel projects. The editor's post-startup pre-resolution goes through the same path, so a large project becomes fully warm sooner.
5454
- **Project startup is significantly faster.** Building the class index now reads files normally instead of memory-mapping them, and the work is parallelized more effectively across your CPU cores. This meaningfully cuts both startup time and CPU use on large Laravel projects, speeding up whole-project analysis and getting the editor ready sooner, with no change in results.
5555
- **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 query, so skipping it removes wasted work from whole-project `analyze` and `fix` runs. The editor's LSP session is unaffected.
56+
- **Faster project startup.** The parts of startup that still ran on a single core now use all of them. Every autoload directory in the project, your own and each vendor package's, is walked in one pass that shares work between cores at the directory level, so a single very large dependency no longer holds up the rest of the scan, and the ignore rules above those directories are compiled once for the whole project instead of once per package. A bundled tool archive such as PHPStan's `.phar` is now read through a memory map with only its file index retained, rather than copied into memory whole. On a large Laravel project this cuts the indexing phase by roughly a quarter and lowers peak memory by around 25 MB. Discovered files are now sorted, so when two files declare the same class name the one that wins is the same on every run instead of depending on the order the filesystem happened to return.
5657
- **Class origin classification no longer re-scans the whole classmap after the fact.** Startup used to look up each discovered class's completion-ranking origin (project, explicit dependency, transitive dependency, core stub) by re-reading and re-parsing `installed.json` a second time and prefix-matching every class's file path against the package list on a single thread. The origin is now attached to a class the moment it is discovered during the already-parallel vendor scan, the same way it already worked for functions and constants, removing both the duplicate parse and the serial pass.
5758

5859
### Removed

docs/todo.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ unlikely to move the needle for most users.
197197
| | **[Performance](todo/performance.md)** | | |
198198
| P46 | [`mago-phpdoc-syntax` cannot parse `@method static (…) name()`](todo/performance.md#p46-mago-phpdoc-syntax-cannot-parse-method-static--name) | Low | Low |
199199
| P30 | [Evaluate migrating parse/resolve/docblock pipeline to `mago-hir`](todo/performance.md#p30-evaluate-migrating-parseresolvedocblock-pipeline-to-mago-hir) (parked — re-evaluated at mago 1.45.0, still no `mago-hir` consumers upstream) | Medium-High | High |
200-
| P49 | [Project init's remaining serial stretches](todo/performance.md#p49-project-inits-remaining-serial-stretches) | Low-Medium | Medium |
201200
| P47 | [The resolved-class cache lock caps concurrent class resolution](todo/performance.md#p47-the-resolved-class-cache-lock-caps-concurrent-class-resolution) | Medium | Medium-High |
202201
| P16 | [Pre-parsed stub format (eliminate raw PHP embedding)](todo/performance.md#p16-pre-parsed-stub-format-eliminate-raw-php-embedding) | High | Medium-High |
203202
| P25 | [`type_mismatch_argument` / `argument_count_mismatch` slow on large single files](todo/performance.md#p25-type_mismatch_argument-argument_count_mismatch-slow-on-large-single-files) | Medium | Medium |

docs/todo/performance.md

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

10501050
---
10511051

1052-
## P49. Project init's remaining serial stretches
1053-
1054-
**Impact: Low-Medium · Effort: Medium**
1055-
1056-
`init_single_project` is down from 0.60 s at 3.2 of 32 cores to 0.21 s
1057-
at 6.2 (mmap contention, per-package collection, and the batch scanners'
1058-
static chunking are fixed), so the diagnostic pass is once again the
1059-
largest share of wall clock. What is left inside init, measured on a
1060-
large Laravel project (32-core machine, release build, warm page cache,
1061-
`utime + stime` from `/proc/self/stat` at each boundary):
1062-
1063-
| Step | Wall | Avg cores |
1064-
| --- | --- | --- |
1065-
| vendor package collection (`collect_package_files`) | 0.060 s | 6.8 |
1066-
| vendor file scan (`scan_files_parallel_full`) | 0.033 s | 20.5 |
1067-
| `scan_autoload_files` | 0.038 s | 3.9 |
1068-
| workspace walk (`ignore` crate) | 0.018 s | 1.1 |
1069-
1070-
Each is small on its own; together they are most of what is left:
1071-
1072-
- **Vendor collection is per-package parallel, but one package is the
1073-
straggler.** `laravel/framework`'s PSR-4 tree is a single work item, so
1074-
the tail of the walk runs on one core. Splitting a package's PSR-4
1075-
roots (or its subdirectories) into separate work items would even it
1076-
out, but the concatenation has to stay in `installed.json` order for
1077-
duplicate FQNs to resolve to the same file.
1078-
- **The workspace walk is single-threaded.** `ignore::WalkBuilder` has
1079-
`build_parallel`, but the walk order feeds the duplicate-FQN
1080-
tie-break, so a parallel walk needs its results sorted to stay
1081-
deterministic.
1082-
- **Reading and manifest-parsing a bundled `.phar` is serial.** With
1083-
phpstan installed this is 17 ms of the 24 ms `require_once` sweep; the
1084-
class scan inside the archive is already parallel.
1085-
1086-
Reproduce with the CPU-sampling loop in the Appendix, or by reading
1087-
`utime + stime` from `/proc/self/stat` at each phase boundary and
1088-
dividing by the phase's wall time. Per-phase numbers for the run as a
1089-
whole (same project): init 0.21 s at 6.2 cores, `discover_user_files`
1090-
0.02 s at 1.1, Phase 1 index 0.02 s at 21.5, Phase 1.5 eager populate
1091-
0.05 s at 4.4, Phase 2 diagnostics 0.47 s at 20.9.
1092-
1093-
---
1094-
10951052
# Remaining anti-pattern fixes
10961053

10971054
Most remaining depth-cap issues were addressed by eager class

0 commit comments

Comments
 (0)