https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/182620
>From fb89c43386a046083d133828ec9ca8c101f00669 Mon Sep 17 00:00:00 2001 From: Paul Kirth <[email protected]> Date: Thu, 18 Dec 2025 14:15:51 -0800 Subject: [PATCH] [clang-doc] Add basic benchmarks for library functionality clang-doc's performance is good, but we suspect it could be better. To track this with more fidelity, we can add a set of GoogleBenchmarks that exercise portions of the library. To start we try to track high level items that we monitor via the TimeTrace functions, and give them their own micro benchmarks. This should give us more confidence that switching out data structures or updating algorthms will have a positive performance impact. Note that an LLM helped generate portions of the benchmarks and parameterize them. Most of the internal logic was written by me, but the LLM was used to handle boilerplate and adaptation to the harness. --- clang-tools-extra/clang-doc/CMakeLists.txt | 4 + .../clang-doc/benchmarks/CMakeLists.txt | 20 ++ .../benchmarks/ClangDocBenchmark.cpp | 220 ++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 clang-tools-extra/clang-doc/benchmarks/CMakeLists.txt create mode 100644 clang-tools-extra/clang-doc/benchmarks/ClangDocBenchmark.cpp diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 7a375d7cd0524..3a6e9ad8fb62b 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -43,3 +43,7 @@ target_link_libraries(clangDoc ) add_subdirectory(tool) + +if (LLVM_INCLUDE_BENCHMARKS) + add_subdirectory(benchmarks) +endif() diff --git a/clang-tools-extra/clang-doc/benchmarks/CMakeLists.txt b/clang-tools-extra/clang-doc/benchmarks/CMakeLists.txt new file mode 100644 index 0000000000000..231fdb6dc0311 --- /dev/null +++ b/clang-tools-extra/clang-doc/benchmarks/CMakeLists.txt @@ -0,0 +1,20 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +get_filename_component(CLANG_DOC_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../../clang-doc REALPATH) +include_directories( + ${CLANG_DOC_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) + +add_benchmark(ClangDocBenchmark ClangDocBenchmark.cpp) + +target_link_libraries(ClangDocBenchmark + PRIVATE + clangDoc + clangTooling + clangBasic + clangAST +) \ No newline at end of file diff --git a/clang-tools-extra/clang-doc/benchmarks/ClangDocBenchmark.cpp b/clang-tools-extra/clang-doc/benchmarks/ClangDocBenchmark.cpp new file mode 100644 index 0000000000000..67e28eda02d33 --- /dev/null +++ b/clang-tools-extra/clang-doc/benchmarks/ClangDocBenchmark.cpp @@ -0,0 +1,220 @@ +#include "../BitcodeReader.h" +#include "../BitcodeWriter.h" +#include "../ClangDoc.h" +#include "../Generators.h" +#include "../Representation.h" +#include "../Serialize.h" +#include "benchmark/benchmark.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Bitstream/BitstreamWriter.h" +#include <string> +#include <vector> + +namespace clang { +namespace doc { + +class BenchmarkVisitor : public RecursiveASTVisitor<BenchmarkVisitor> { +public: + explicit BenchmarkVisitor(const FunctionDecl *&Func) : Func(Func) {} + + bool VisitFunctionDecl(const FunctionDecl *D) { + if (D->getName() == "f") { + Func = D; + return false; + } + return true; + } + +private: + const FunctionDecl *&Func; +}; + +// --- Mapper Benchmarks --- + +static void BM_EmitInfoFunction(benchmark::State &State) { + std::string Code = "void f() {}"; + std::unique_ptr<clang::ASTUnit> AST = clang::tooling::buildASTFromCode(Code); + const FunctionDecl *Func = nullptr; + BenchmarkVisitor Visitor(Func); + Visitor.TraverseDecl(AST->getASTContext().getTranslationUnitDecl()); + assert(Func); + + clang::comments::FullComment *FC = nullptr; + Location Loc; + + for (auto _ : State) { + auto Result = serialize::emitInfo(Func, FC, Loc, /*PublicOnly=*/false); + benchmark::DoNotOptimize(Result); + } +} +BENCHMARK(BM_EmitInfoFunction); + +static void BM_Mapper_Scale(benchmark::State &State) { + std::string Code; + for (int i = 0; i < State.range(0); ++i) { + Code += "void f" + std::to_string(i) + "() {}\n"; + } + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + DiagnosticOptions DiagOpts; + DiagnosticsEngine Diags(DiagID, DiagOpts, new IgnoringDiagConsumer()); + + for (auto _ : State) { + tooling::InMemoryToolResults Results; + tooling::ExecutionContext ECtx(&Results); + ClangDocContext CDCtx(&ECtx, "test-project", false, "", "", "", "", "", {}, + Diags, false); + auto ActionFactory = doc::newMapperActionFactory(CDCtx); + std::unique_ptr<FrontendAction> Action = ActionFactory->create(); + tooling::runToolOnCode(std::move(Action), Code, "test.cpp"); + } +} +BENCHMARK(BM_Mapper_Scale)->Range(10, 10000); + +// --- Reducer Benchmarks --- + +static void BM_SerializeFunctionInfo(benchmark::State &State) { + auto I = std::make_unique<FunctionInfo>(); + I->Name = "f"; + I->DefLoc = Location(0, 0, "test.cpp"); + I->ReturnType = TypeInfo("void"); + I->IT = InfoType::IT_function; + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + DiagnosticOptions DiagOpts; + DiagnosticsEngine Diags(DiagID, DiagOpts, new IgnoringDiagConsumer()); + + std::unique_ptr<Info> InfoPtr = std::move(I); + + for (auto _ : State) { + auto Result = serialize::serialize(InfoPtr, Diags); + benchmark::DoNotOptimize(Result); + } +} +BENCHMARK(BM_SerializeFunctionInfo); + +static void BM_MergeInfos_Scale(benchmark::State &State) { + SymbolID USR = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + + for (auto _ : State) { + State.PauseTiming(); + std::vector<std::unique_ptr<Info>> Input; + Input.reserve(State.range(0)); + for (int i = 0; i < State.range(0); ++i) { + auto I = std::make_unique<FunctionInfo>(); + I->Name = "f"; + I->USR = USR; + I->DefLoc = Location(10, i, "test.cpp"); + Input.push_back(std::move(I)); + } + State.ResumeTiming(); + + auto Result = doc::mergeInfos(Input); + if (!Result) { + State.SkipWithError("mergeInfos failed"); + llvm::consumeError(Result.takeError()); + } + benchmark::DoNotOptimize(Result); + } +} +BENCHMARK(BM_MergeInfos_Scale)->Range(2, 10000); + +static void BM_BitcodeReader_Scale(benchmark::State &State) { + int NumRecords = State.range(0); + + SmallString<0> Buffer; + llvm::BitstreamWriter Stream(Buffer); + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + DiagnosticOptions DiagOpts; + DiagnosticsEngine Diags(DiagID, DiagOpts, new IgnoringDiagConsumer()); + + ClangDocBitcodeWriter Writer(Stream, Diags); + for (int i = 0; i < NumRecords; ++i) { + RecordInfo RI; + RI.Name = "Record" + std::to_string(i); + RI.USR = {(uint8_t)(i & 0xFF)}; + Writer.emitBlock(RI); + } + + std::string BitcodeData = Buffer.str().str(); + + for (auto _ : State) { + llvm::BitstreamCursor Cursor(llvm::ArrayRef<uint8_t>( + (const uint8_t *)BitcodeData.data(), BitcodeData.size())); + ClangDocBitcodeReader Reader(Cursor, Diags); + auto Result = Reader.readBitcode(); + if (!Result) { + State.SkipWithError("readBitcode failed"); + llvm::consumeError(Result.takeError()); + } + benchmark::DoNotOptimize(Result); + } +} +BENCHMARK(BM_BitcodeReader_Scale)->Range(10, 10000); + +// --- Generator Benchmarks --- + +static void BM_JSONGenerator_Scale(benchmark::State &State) { + auto G = doc::findGeneratorByName("json"); + if (!G) { + State.SkipWithError("JSON Generator not found"); + llvm::consumeError(G.takeError()); + return; + } + + int NumRecords = State.range(0); + auto NI = std::make_unique<NamespaceInfo>(); + NI->Name = "GlobalNamespace"; + for (int i = 0; i < NumRecords; ++i) { + NI->Children.Records.emplace_back(SymbolID{(uint8_t)(i & 0xFF)}, + "Record" + std::to_string(i), + InfoType::IT_record); + } + + IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); + DiagnosticOptions DiagOpts; + DiagnosticsEngine Diags(DiagID, DiagOpts, new IgnoringDiagConsumer()); + ClangDocContext CDCtx(nullptr, "test-project", false, "", "", "", "", "", {}, + Diags, false); + + std::string Output; + llvm::raw_string_ostream OS(Output); + + for (auto _ : State) { + Output.clear(); + auto Err = (*G)->generateDocForInfo(NI.get(), OS, CDCtx); + if (Err) { + State.SkipWithError("generateDocForInfo failed"); + llvm::consumeError(std::move(Err)); + } + benchmark::DoNotOptimize(Output); + } +} +BENCHMARK(BM_JSONGenerator_Scale)->Range(10, 10000); + +// --- Index Benchmarks --- + +static void BM_Index_Insertion(benchmark::State &State) { + for (auto _ : State) { + Index Idx; + for (int i = 0; i < State.range(0); ++i) { + RecordInfo I; + I.Name = "Record" + std::to_string(i); + // Vary USR to ensure unique entries + I.USR = {(uint8_t)(i & 0xFF), (uint8_t)((i >> 8) & 0xFF)}; + I.Path = "path/to/record"; + Generator::addInfoToIndex(Idx, &I); + } + benchmark::DoNotOptimize(Idx); + } +} +BENCHMARK(BM_Index_Insertion)->Range(10, 10000); + +} // namespace doc +} // namespace clang + +BENCHMARK_MAIN(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
