mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-25 17:30:16 +01:00
I was discussing some details of cross-compiler lex performance. Since we were talking about LoC initially, and lex performance especially will differ based on bytes and tokens being lexed, throwing in some stats for how we're processing those. Here's some example output: ``` ---------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations Bytes Lines Tokens ---------------------------------------------------------------------------------------------------------------------------- BM_CompileAPIFileDenseDecls<Phase::Lex>/256 31828 ns 31798 ns 22528 165.64M/s 6.13247M/s 34.6249M/s BM_CompileAPIFileDenseDecls<Phase::Lex>/1024 147513 ns 147434 ns 5120 220.363M/s 6.64025M/s 39.014M/s BM_CompileAPIFileDenseDecls<Phase::Lex>/4096 611530 ns 610985 ns 1280 232.22M/s 6.59264M/s 39.0501M/s BM_CompileAPIFileDenseDecls<Phase::Lex>/16384 2645671 ns 2643411 ns 320 231.122M/s 6.17119M/s 36.616M/s BM_CompileAPIFileDenseDecls<Phase::Lex>/65536 11593324 ns 11587201 ns 64 217.864M/s 5.64934M/s 33.5378M/s BM_CompileAPIFileDenseDecls<Phase::Lex>/262144 60338069 ns 60313976 ns 16 169.444M/s 4.34607M/s 25.8032M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/256 53355 ns 53308 ns 13312 98.8029M/s 3.65798M/s 20.6535M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/1024 253979 ns 253818 ns 3072 128.001M/s 3.8571M/s 22.6619M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/4096 1052984 ns 1052427 ns 768 134.815M/s 3.82734M/s 22.6705M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/16384 4364730 ns 4362756 ns 192 140.038M/s 3.73915M/s 22.1857M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/65536 19419562 ns 19413505 ns 48 130.035M/s 3.37188M/s 20.0175M/s BM_CompileAPIFileDenseDecls<Phase::Parse>/262144 89023213 ns 88979387 ns 8 114.856M/s 2.94595M/s 17.4905M/s BM_CompileAPIFileDenseDecls<Phase::Check>/256 676254 ns 675605 ns 1024 7.79597M/s 288.63k/s 1.62965M/s BM_CompileAPIFileDenseDecls<Phase::Check>/1024 1412608 ns 1411876 ns 1024 23.0112M/s 693.404k/s 4.07401M/s BM_CompileAPIFileDenseDecls<Phase::Check>/4096 4333665 ns 4331240 ns 256 32.7581M/s 929.988k/s 5.50858M/s BM_CompileAPIFileDenseDecls<Phase::Check>/16384 16566625 ns 16553982 ns 64 36.9065M/s 985.443k/s 5.84699M/s BM_CompileAPIFileDenseDecls<Phase::Check>/65536 68609701 ns 68542189 ns 16 36.8304M/s 955.032k/s 5.66963M/s BM_CompileAPIFileDenseDecls<Phase::Check>/262144 302899379 ns 302596672 ns 8 33.7739M/s 866.265k/s 5.14313M/s ``` Also note, this is the discussion that led to [me looking at bytes per token](https://discord.com/channels/655572317891461132/655578254970716160/1295803122844700786)
164 lines
6.0 KiB
C++
164 lines
6.0 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include <benchmark/benchmark.h>
|
|
|
|
#include <string>
|
|
|
|
#include "testing/base/global_exe_path.h"
|
|
#include "testing/base/source_gen.h"
|
|
#include "toolchain/driver/driver.h"
|
|
#include "toolchain/install/install_paths_test_helpers.h"
|
|
#include "toolchain/testing/compile_helper.h"
|
|
|
|
namespace Carbon::Testing {
|
|
namespace {
|
|
|
|
// Helper used to benchmark compilation across different phases.
|
|
//
|
|
// Handles setting up the compiler's driver, locating the prelude, and managing
|
|
// a VFS in which the compilations occur.
|
|
class CompileBenchmark {
|
|
public:
|
|
CompileBenchmark()
|
|
: installation_(InstallPaths::MakeForBazelRunfiles(GetExePath())),
|
|
driver_(fs_, &installation_, llvm::outs(), llvm::errs()) {
|
|
AddPreludeFilesToVfs(installation_, &fs_);
|
|
}
|
|
|
|
// Setup a set of source files in the VFS for the driver. Each string input is
|
|
// materialized into a virtual file and a list of the virtual filenames is
|
|
// returned.
|
|
auto SetUpFiles(llvm::ArrayRef<std::string> sources)
|
|
-> llvm::OwningArrayRef<std::string> {
|
|
llvm::OwningArrayRef<std::string> file_names(sources.size());
|
|
for (ssize_t i : llvm::seq<ssize_t>(sources.size())) {
|
|
file_names[i] = llvm::formatv("file_{0}.carbon", i).str();
|
|
fs_.addFile(file_names[i], /*ModificationTime=*/0,
|
|
llvm::MemoryBuffer::getMemBuffer(sources[i]));
|
|
}
|
|
return file_names;
|
|
}
|
|
|
|
auto driver() -> Driver& { return driver_; }
|
|
auto gen() -> SourceGen& { return gen_; }
|
|
|
|
private:
|
|
llvm::vfs::InMemoryFileSystem fs_;
|
|
const InstallPaths installation_;
|
|
Driver driver_;
|
|
|
|
SourceGen gen_;
|
|
};
|
|
|
|
// An enumerator used to select compilation phases to benchmark.
|
|
enum class Phase {
|
|
Lex,
|
|
Parse,
|
|
Check,
|
|
};
|
|
|
|
// Maps the enumerator for a compilation phase into a specific `compile` command
|
|
// line flag.
|
|
static auto PhaseFlag(Phase phase) -> llvm::StringRef {
|
|
switch (phase) {
|
|
case Phase::Lex:
|
|
return "--phase=lex";
|
|
case Phase::Parse:
|
|
return "--phase=parse";
|
|
case Phase::Check:
|
|
return "--phase=check";
|
|
}
|
|
}
|
|
|
|
// Benchmark on multiple files of the same size but with different source code
|
|
// in order to avoid branch prediction perfectly learning a particular file's
|
|
// structure and shape, and to get closer to a cache-cold benchmark number which
|
|
// is what we generally expect to care about in practice. We enforce an upper
|
|
// bound to avoid excessive benchmark time and a lower bound to avoid anchoring
|
|
// on a single source file that may have unrepresentative content.
|
|
//
|
|
// For simplicity, we compute a number of files from the target line count as a
|
|
// heuristic.
|
|
static auto ComputeFileCount(int target_lines) -> int {
|
|
#ifndef NDEBUG
|
|
// Use a smaller number of files in debug builds where compiles are slower.
|
|
return std::max(1, std::min(8, (1024 * 1024) / target_lines));
|
|
#else
|
|
return std::max(8, std::min(1024, (1024 * 1024) / target_lines));
|
|
#endif
|
|
}
|
|
|
|
template <Phase P>
|
|
static auto BM_CompileAPIFileDenseDecls(benchmark::State& state) -> void {
|
|
CompileBenchmark bench;
|
|
int target_lines = state.range(0);
|
|
int num_files = ComputeFileCount(target_lines);
|
|
llvm::OwningArrayRef<std::string> sources(num_files);
|
|
|
|
// Create a collection of random source files. Compute average statistics for
|
|
// counters for compilation speed.
|
|
CompileHelper compile_helper;
|
|
double total_bytes = 0.0;
|
|
double total_tokens = 0.0;
|
|
double total_lines = 0.0;
|
|
for (std::string& source : sources) {
|
|
source = bench.gen().GenAPIFileDenseDecls(target_lines,
|
|
SourceGen::DenseDeclParams{});
|
|
total_bytes += source.size();
|
|
total_tokens += compile_helper.GetTokenizedBuffer(source).size();
|
|
total_lines += llvm::count(source, '\n');
|
|
};
|
|
state.counters["Bytes"] =
|
|
benchmark::Counter(total_bytes / sources.size(),
|
|
benchmark::Counter::kIsIterationInvariantRate);
|
|
state.counters["Tokens"] =
|
|
benchmark::Counter(total_tokens / sources.size(),
|
|
benchmark::Counter::kIsIterationInvariantRate);
|
|
state.counters["Lines"] =
|
|
benchmark::Counter(total_lines / sources.size(),
|
|
benchmark::Counter::kIsIterationInvariantRate);
|
|
|
|
// Set up the sources as files for compilation.
|
|
llvm::OwningArrayRef<std::string> file_names = bench.SetUpFiles(sources);
|
|
CARBON_CHECK(static_cast<int>(file_names.size()) == num_files);
|
|
|
|
// We benchmark in batches of files to avoid benchmarking any peculiarities of
|
|
// a single file.
|
|
while (state.KeepRunningBatch(num_files)) {
|
|
for (ssize_t i = 0; i < num_files;) {
|
|
// We block optimizing `i` as that has proven both more effective at
|
|
// blocking the loop from being optimized away and avoiding disruption of
|
|
// the generated code that we're benchmarking.
|
|
benchmark::DoNotOptimize(i);
|
|
|
|
bool success = bench.driver()
|
|
.RunCommand({"compile", PhaseFlag(P), file_names[i]})
|
|
.success;
|
|
CARBON_DCHECK(success);
|
|
|
|
// We use the compilation success to step through the file names,
|
|
// establishing a dependency between each lookup. This doesn't fully allow
|
|
// us to measure latency rather than throughput, but minimizes any skew in
|
|
// measurements from speculating the start of the next compilation.
|
|
i += static_cast<ssize_t>(success);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Benchmark from 256-line test cases through 256k line test cases, and for each
|
|
// phase of compilation.
|
|
BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Lex>)
|
|
->RangeMultiplier(4)
|
|
->Range(256, static_cast<int64_t>(256 * 1024));
|
|
BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Parse>)
|
|
->RangeMultiplier(4)
|
|
->Range(256, static_cast<int64_t>(256 * 1024));
|
|
BENCHMARK(BM_CompileAPIFileDenseDecls<Phase::Check>)
|
|
->RangeMultiplier(4)
|
|
->Range(256, static_cast<int64_t>(256 * 1024));
|
|
|
|
} // namespace
|
|
} // namespace Carbon::Testing
|