//===-- Benchmark ---------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "JSON.h" #include "LibcBenchmark.h" #include "LibcMemoryBenchmark.h" #include "MemorySizeDistributions.h" #include "src/__support/macros/config.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" #include #include namespace LIBC_NAMESPACE_DECL { extern void *memcpy(void *__restrict, const void *__restrict, size_t); extern void *memmove(void *, const void *, size_t); extern void *memset(void *, int, size_t); extern void bzero(void *, size_t); extern int memcmp(const void *, const void *, size_t); extern int bcmp(const void *, const void *, size_t); } // namespace LIBC_NAMESPACE_DECL namespace llvm { namespace libc_benchmarks { static cl::opt StudyName("study-name", cl::desc("The name for this study"), cl::Required); static cl::opt SizeDistributionName("size-distribution-name", cl::desc("The name of the distribution to use")); static cl::opt SweepMode( "sweep-mode", cl::desc( "If set, benchmark all sizes from sweep-min-size to sweep-max-size")); static cl::opt SweepMinSize("sweep-min-size", cl::desc("The minimum size to use in sweep-mode"), cl::init(0)); static cl::opt SweepMaxSize("sweep-max-size", cl::desc("The maximum size to use in sweep-mode"), cl::init(256)); static cl::opt AlignedAccess("aligned-access", cl::desc("The alignment to use when accessing the buffers\n" "Default is unaligned\n" "Use 0 to disable address randomization"), cl::init(1)); static cl::opt Output("output", cl::desc("Specify output filename"), cl::value_desc("filename"), cl::init("-")); static cl::opt NumTrials("num-trials", cl::desc("The number of benchmarks run to perform"), cl::init(1)); #if defined(LIBC_BENCHMARK_FUNCTION_MEMCPY) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_MEMCPY using BenchmarkSetup = CopySetup; #elif defined(LIBC_BENCHMARK_FUNCTION_MEMMOVE) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_MEMMOVE using BenchmarkSetup = MoveSetup; #elif defined(LIBC_BENCHMARK_FUNCTION_MEMSET) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_MEMSET using BenchmarkSetup = SetSetup; #elif defined(LIBC_BENCHMARK_FUNCTION_BZERO) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_BZERO using BenchmarkSetup = SetSetup; #elif defined(LIBC_BENCHMARK_FUNCTION_MEMCMP) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_MEMCMP using BenchmarkSetup = ComparisonSetup; #elif defined(LIBC_BENCHMARK_FUNCTION_BCMP) #define LIBC_BENCHMARK_FUNCTION LIBC_BENCHMARK_FUNCTION_BCMP using BenchmarkSetup = ComparisonSetup; #else #error "Missing LIBC_BENCHMARK_FUNCTION_XXX definition" #endif struct MemfunctionBenchmarkBase : public BenchmarkSetup { MemfunctionBenchmarkBase() : ReportProgress(isatty(fileno(stdout))) {} virtual ~MemfunctionBenchmarkBase() {} virtual Study run() = 0; CircularArrayRef generate_batch(size_t iterations) { randomize(); return cycle(ArrayRef(parameters), iterations); } protected: Study createStudy() { Study study; // Setup study. study.study_name = StudyName; Runtime &ri = study.runtime; ri.host = HostState::get(); ri.buffer_size = buffer_size; ri.batch_parameter_count = batch_size; BenchmarkOptions &bo = ri.benchmark_options; bo.min_duration = std::chrono::milliseconds(1); bo.max_duration = std::chrono::seconds(1); bo.max_iterations = 10'000'000U; bo.min_samples = 4; bo.max_samples = 1000; bo.epsilon = 0.01; // 1% bo.scaling_factor = 1.4; StudyConfiguration &sc = study.configuration; sc.num_trials = NumTrials; sc.is_sweep_mode = SweepMode; sc.access_alignment = MaybeAlign(AlignedAccess); sc.function = LIBC_BENCHMARK_FUNCTION_NAME; return study; } void runTrials(const BenchmarkOptions &options, std::vector &measurements) { for (size_t i = 0; i < NumTrials; ++i) { const BenchmarkResult result = benchmark( options, *this, [this](ParameterBatch::ParameterType parameter) { return call(parameter, LIBC_BENCHMARK_FUNCTION); }); measurements.push_back(result.best_guess); reportProgress(measurements); } } virtual void randomize() = 0; private: bool ReportProgress; void reportProgress(const std::vector &Measurements) { if (!ReportProgress) return; static size_t LastPercent = -1; const size_t TotalSteps = Measurements.capacity(); const size_t Steps = Measurements.size(); const size_t Percent = 100 * Steps / TotalSteps; if (Percent == LastPercent) return; LastPercent = Percent; size_t i = 0; errs() << '['; for (; i <= Percent; ++i) errs() << '#'; for (; i <= 100; ++i) errs() << '_'; errs() << "] " << Percent << '%' << '\r'; } }; struct MemfunctionBenchmarkSweep final : public MemfunctionBenchmarkBase { MemfunctionBenchmarkSweep() : offset_sampler(MemfunctionBenchmarkBase::buffer_size, SweepMaxSize, MaybeAlign(AlignedAccess)) {} virtual void randomize() override { for (auto &p : parameters) { p.offset_bytes = offset_sampler(gen); p.size_bytes = current_sweep_size; check_valid(p); } } virtual Study run() override { Study study = createStudy(); study.configuration.sweep_mode_max_size = SweepMaxSize; BenchmarkOptions &bo = study.runtime.benchmark_options; bo.min_duration = std::chrono::milliseconds(1); bo.initial_iterations = 100; auto &measurements = study.measurements; measurements.reserve(NumTrials * SweepMaxSize); for (size_t Size = SweepMinSize; Size <= SweepMaxSize; ++Size) { current_sweep_size = Size; runTrials(bo, measurements); } return study; } private: size_t current_sweep_size = 0; OffsetDistribution offset_sampler; std::mt19937_64 gen; }; struct MemfunctionBenchmarkDistribution final : public MemfunctionBenchmarkBase { MemfunctionBenchmarkDistribution(MemorySizeDistribution distribution_arg) : distribution(distribution_arg), probabilities(distribution_arg.probabilities), size_sampler(probabilities.begin(), probabilities.end()), offset_sampler(MemfunctionBenchmarkBase::buffer_size, probabilities.size() - 1, MaybeAlign(AlignedAccess)) {} virtual void randomize() override { for (auto &p : parameters) { p.offset_bytes = offset_sampler(gen); p.size_bytes = size_sampler(gen); check_valid(p); } } virtual Study run() override { Study study = createStudy(); study.configuration.size_distribution_name = distribution.name.str(); BenchmarkOptions &bo = study.runtime.benchmark_options; bo.min_duration = std::chrono::milliseconds(10); bo.initial_iterations = batch_size * 10; auto &measurements = study.measurements; measurements.reserve(NumTrials); runTrials(bo, measurements); return study; } private: MemorySizeDistribution distribution; ArrayRef probabilities; std::discrete_distribution size_sampler; OffsetDistribution offset_sampler; std::mt19937_64 gen; }; void writeStudy(const Study &S) { std::error_code EC; raw_fd_ostream FOS(Output, EC); if (EC) report_fatal_error(Twine("Could not open file: ") .concat(EC.message()) .concat(", ") .concat(Output)); json::OStream JOS(FOS); serializeToJson(S, JOS); FOS << "\n"; } void main() { checkRequirements(); if (!isPowerOf2_32(AlignedAccess)) report_fatal_error(AlignedAccess.ArgStr + Twine(" must be a power of two or zero")); const bool HasDistributionName = !SizeDistributionName.empty(); if (SweepMode && HasDistributionName) report_fatal_error("Select only one of `--" + Twine(SweepMode.ArgStr) + "` or `--" + Twine(SizeDistributionName.ArgStr) + "`"); std::unique_ptr Benchmark; if (SweepMode) Benchmark.reset(new MemfunctionBenchmarkSweep()); else Benchmark.reset(new MemfunctionBenchmarkDistribution(getDistributionOrDie( BenchmarkSetup::get_distributions(), SizeDistributionName))); writeStudy(Benchmark->run()); } } // namespace libc_benchmarks } // namespace llvm #ifndef NDEBUG #error For reproducibility benchmarks should not be compiled in DEBUG mode. #endif int main(int argc, char **argv) { llvm::cl::ParseCommandLineOptions(argc, argv); llvm::libc_benchmarks::main(); return EXIT_SUCCESS; }