compile_benchmark.cpp 6.1 KB

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