compile_benchmark.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include <benchmark/benchmark.h>
  5. #include <string>
  6. #include "testing/base/global_exe_path.h"
  7. #include "testing/base/source_gen.h"
  8. #include "toolchain/driver/driver.h"
  9. namespace Carbon::Testing {
  10. namespace {
  11. // Helper used to benchmark compilation across different phases.
  12. //
  13. // Handles setting up the compiler's driver, locating the prelude, and managing
  14. // a VFS in which the compilations occur.
  15. class CompileBenchmark {
  16. public:
  17. CompileBenchmark()
  18. : installation_(InstallPaths::MakeForBazelRunfiles(GetExePath())),
  19. driver_(fs_, &installation_, llvm::outs(), llvm::errs()) {
  20. // Load the prelude into our VFS.
  21. //
  22. // TODO: Factor this and analogous code in file_test into a Driver helper.
  23. auto prelude =
  24. Driver::FindPreludeFiles(installation_.core_package(), llvm::errs());
  25. CARBON_CHECK(!prelude.empty());
  26. for (const auto& path : prelude) {
  27. llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> file =
  28. llvm::MemoryBuffer::getFile(path);
  29. CARBON_CHECK(file) << file.getError().message();
  30. CARBON_CHECK(fs_.addFile(path, /*ModificationTime=*/0, std::move(*file)))
  31. << "Duplicate file: " << path;
  32. }
  33. }
  34. // Setup a set of source files in the VFS for the driver. Each string input is
  35. // materialized into a virtual file and a list of the virtual filenames is
  36. // returned.
  37. auto SetUpFiles(llvm::ArrayRef<std::string> sources)
  38. -> llvm::OwningArrayRef<std::string> {
  39. llvm::OwningArrayRef<std::string> file_names(sources.size());
  40. for (ssize_t i : llvm::seq<ssize_t>(sources.size())) {
  41. file_names[i] = llvm::formatv("file_{0}.carbon", i).str();
  42. fs_.addFile(file_names[i], /*ModificationTime=*/0,
  43. llvm::MemoryBuffer::getMemBuffer(sources[i]));
  44. }
  45. return file_names;
  46. }
  47. auto driver() -> Driver& { return driver_; }
  48. auto gen() -> SourceGen& { return gen_; }
  49. private:
  50. llvm::vfs::InMemoryFileSystem fs_;
  51. const InstallPaths installation_;
  52. Driver driver_;
  53. SourceGen gen_;
  54. };
  55. // An enumerator used to select compilation phases to benchmark.
  56. enum class Phase {
  57. Lex,
  58. Parse,
  59. Check,
  60. };
  61. // Maps the enumerator for a compilation phase into a specific `compile` command
  62. // line flag.
  63. static auto PhaseFlag(Phase phase) -> llvm::StringRef {
  64. switch (phase) {
  65. case Phase::Lex:
  66. return "--phase=lex";
  67. case Phase::Parse:
  68. return "--phase=parse";
  69. case Phase::Check:
  70. return "--phase=check";
  71. }
  72. }
  73. // Benchmark on multiple files of the same size but with different source code
  74. // in order to avoid branch prediction perfectly learning a particular file's
  75. // structure and shape, and to get closer to a cache-cold benchmark number which
  76. // is what we generally expect to care about in practice. We enforce an upper
  77. // bound to avoid excessive benchmark time and a lower bound to avoid anchoring
  78. // on a single source file that may have unrepresentative content.
  79. //
  80. // For simplicity, we compute a number of files from the target line count as a
  81. // heuristic.
  82. static auto ComputeFileCount(int target_lines) -> int {
  83. #ifndef NDEBUG
  84. // Use a smaller number of files in debug builds where compiles are slower.
  85. return std::max(1, std::min(8, (1024 * 1024) / target_lines));
  86. #else
  87. return std::max(8, std::min(1024, (1024 * 1024) / target_lines));
  88. #endif
  89. }
  90. template <Phase P>
  91. static auto BM_CompileAPIFileDenseDecls(benchmark::State& state) -> void {
  92. CompileBenchmark bench;
  93. int target_lines = state.range(0);
  94. int num_files = ComputeFileCount(target_lines);
  95. llvm::OwningArrayRef<std::string> sources(num_files);
  96. // Create a collection of random source files. Average the actual number of
  97. // lines resulting so we can use that to compute the compilation speed as a
  98. // line-rate counter.
  99. double avg_lines = 0.0;
  100. for (std::string& source : sources) {
  101. source = bench.gen().GenAPIFileDenseDecls(target_lines,
  102. SourceGen::DenseDeclParams{});
  103. avg_lines += llvm::count(source, '\n');
  104. }
  105. avg_lines /= sources.size();
  106. // Setup the sources as files for compilation.
  107. llvm::OwningArrayRef<std::string> file_names = bench.SetUpFiles(sources);
  108. CARBON_CHECK(static_cast<int>(file_names.size()) == num_files);
  109. // We benchmark in batches of files to avoid benchmarking any peculiarities of
  110. // a single file.
  111. while (state.KeepRunningBatch(num_files)) {
  112. for (ssize_t i = 0; i < num_files;) {
  113. // We block optimizing `i` as that has proven both more effective at
  114. // blocking the loop from being optimized away and avoiding disruption of
  115. // the generated code that we're benchmarking.
  116. benchmark::DoNotOptimize(i);
  117. bool success = bench.driver()
  118. .RunCommand({"compile", PhaseFlag(P), file_names[i]})
  119. .success;
  120. CARBON_DCHECK(success);
  121. // We use the compilation success to step through the file names,
  122. // establishing a dependency between each lookup. This doesn't fully allow
  123. // us to measure latency rather than throughput, but minimizes any skew in
  124. // measurements from speculating the start of the next compilation.
  125. i += static_cast<ssize_t>(success);
  126. }
  127. }
  128. // Compute the line-rate of these compilations.
  129. state.counters["Lines"] = benchmark::Counter(
  130. avg_lines, benchmark::Counter::kIsIterationInvariantRate);
  131. }
  132. // Benchmark from 256-line test cases through 256k line test cases, and for each
  133. // phase of compilation.
  134. BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Lex>)
  135. ->RangeMultiplier(4)
  136. ->Range(256, static_cast<int64_t>(256 * 1024));
  137. BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Parse>)
  138. ->RangeMultiplier(4)
  139. ->Range(256, static_cast<int64_t>(256 * 1024));
  140. BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Check>)
  141. ->RangeMultiplier(4)
  142. ->Range(256, static_cast<int64_t>(256 * 1024));
  143. } // namespace
  144. } // namespace Carbon::Testing