https://github.com/StefanoD updated https://github.com/llvm/llvm-project/pull/213349
>From 7ff9a040d3cb6c2a5e35fa8e873f5133028c770b Mon Sep 17 00:00:00 2001 From: Stefano Di Martino <[email protected]> Date: Fri, 31 Jul 2026 21:10:21 +0200 Subject: [PATCH] [clangd] Report preamble and AST build stats in --check clangd --check is the tool users are pointed at when clangd feels slow, but it reported nothing about where the time actually went. Profiling clangd opening large translation units in llvm-project shows preamble construction is 64-78% of the cold-open CPU cost (and PCH serialization roughly a fifth of that), yet none of it was observable from --check. There was also a real asymmetry: --check called buildPreamble() without a PreambleBuildStats, and buildPreamble only installs the TimerFS wrapper when Stats is non-null (Preamble.cpp). So unlike the LSP path (TUScheduler.cpp), --check could not report filesystem time at all, and was measuring a slightly different code path from the one the server runs. Pass a PreambleBuildStats and report it, and time ParsedAST::build. Example on clang/lib/AST/ASTContext.cpp: Preamble stats: 5.92s total, of which 0.03s (1%) in the filesystem; 178455475 bytes memory at peak, 62794336 bytes serialized AST stats: 2.64s to build The filesystem ratio is directly useful: it distinguishes "clangd is slow because your headers are on a network filesystem" from "clangd is slow because this TU is genuinely expensive to parse". Installing TimerFS on the --check path costs nothing measurable. A/B of the unpatched vs patched binary, same build, both pinned to one core with the performance governor and boost disabled (AMD Ryzen 7 1700, 21 runs each, llvm/lib/Transforms/Scalar/GVN.cpp, --check-locations=0): Benchmark 1 (21 runs): clangd-baseline ... --check=GVN.cpp measurement mean +- s min ... max delta wall_time 4.36s +- 27.8ms 4.31s ... 4.41s 0% peak_rss 241MB +- 214KB 241MB ... 242MB 0% cpu_cycles 12.1G +- 77.0M 12.0G ... 12.2G 0% instructions 12.1G +- 7.53M 12.1G ... 12.2G 0% cache_references 1.40G +- 5.21M 1.39G ... 1.41G 0% cache_misses 426M +- 2.33M 421M ... 429M 0% branch_misses 117M +- 718K 116M ... 119M 0% Benchmark 2 (21 runs): clangd ... --check=GVN.cpp measurement mean +- s min ... max delta wall_time 4.34s +- 40.6ms 4.30s ... 4.51s - 0.4% +- 0.5% peak_rss 241MB +- 198KB 241MB ... 242MB + 0.0% +- 0.1% cpu_cycles 12.0G +- 115M 11.9G ... 12.5G - 0.4% +- 0.5% instructions 12.1G +- 6.09M 12.1G ... 12.2G + 0.0% +- 0.0% cache_references 1.40G +- 4.74M 1.39G ... 1.41G - 0.0% +- 0.2% cache_misses 424M +- 2.48M 420M ... 428M - 0.3% +- 0.4% branch_misses 117M +- 618K 116M ... 119M - 0.2% +- 0.4% No delta is statistically significant (poop reported no significance markers on any counter); instructions are unchanged at +0.0% +- 0.0%. Test plan: check.test extended to cover both new lines; passes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- clang-tools-extra/clangd/test/GH75115.test | 2 ++ clang-tools-extra/clangd/test/check.test | 2 ++ clang-tools-extra/clangd/tool/Check.cpp | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/test/GH75115.test b/clang-tools-extra/clangd/test/GH75115.test index dc86f5b9a6cee..2d4ffc25178c2 100644 --- a/clang-tools-extra/clangd/test/GH75115.test +++ b/clang-tools-extra/clangd/test/GH75115.test @@ -5,7 +5,9 @@ // CHECK: Building preamble... // CHECK-NEXT: Built preamble // CHECK-NEXT: Indexing headers... +// CHECK-NEXT: Preamble stats: // CHECK-NEXT: Building AST... +// CHECK-NEXT: AST stats: // CHECK-NEXT: Indexing AST... // CHECK-NEXT: Building inlay hints // CHECK-NEXT: semantic highlighting diff --git a/clang-tools-extra/clangd/test/check.test b/clang-tools-extra/clangd/test/check.test index 60a542fba8bea..9f5cda1ac332e 100644 --- a/clang-tools-extra/clangd/test/check.test +++ b/clang-tools-extra/clangd/test/check.test @@ -5,7 +5,9 @@ // CHECK: internal (cc1) args are: -cc1 // CHECK: Building preamble... // CHECK: Built preamble +// CHECK: Preamble stats: {{.*}}s total, of which {{.*}}s ({{.*}}) in the filesystem; {{[0-9]+}} bytes memory at peak, {{[0-9]+}} bytes serialized // CHECK: Building AST... +// CHECK: AST stats: {{.*}}s to build // CHECK: Testing features at each token // CHECK-DAG: tweak: ExpandDeducedType // CHECK-DAG: hover: true diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp index 03c4f58a49c9c..f1b63afcaac38 100644 --- a/clang-tools-extra/clangd/tool/Check.cpp +++ b/clang-tools-extra/clangd/tool/Check.cpp @@ -245,6 +245,7 @@ class Checker { // Build preamble and AST, and index them. bool buildAST() { log("Building preamble..."); + PreambleBuildStats PreambleStats; Preamble = buildPreamble( File, *Invocation, Inputs, /*StoreInMemory=*/true, [&](CapturedASTCtx Ctx, @@ -254,16 +255,29 @@ class Checker { log("Indexing headers..."); Index.updatePreamble(File, /*Version=*/"null", Ctx.getASTContext(), Ctx.getPreprocessor(), *PI); - }); + }, + &PreambleStats); if (!Preamble) { elog("Failed to build preamble"); return false; } + log("Preamble stats: {0:F2}s total, of which {1:F2}s ({2:P0}) in the " + "filesystem; {3} bytes memory at peak, {4} bytes serialized", + PreambleStats.TotalBuildTime, PreambleStats.FileSystemTime, + PreambleStats.TotalBuildTime > 0 + ? PreambleStats.FileSystemTime / PreambleStats.TotalBuildTime + : 0.0, + PreambleStats.BuildSize, PreambleStats.SerializedSize); ErrCount += showErrors(Preamble->Diags); log("Building AST..."); + auto ASTStart = std::chrono::steady_clock::now(); AST = ParsedAST::build(File, Inputs, std::move(Invocation), /*InvocationDiags=*/std::vector<Diag>{}, Preamble); + log("AST stats: {0:F2}s to build", + std::chrono::duration<double>(std::chrono::steady_clock::now() - + ASTStart) + .count()); if (!AST) { elog("Failed to build AST"); return false; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
