| //===-- 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 <cstring> | |
| #include <unistd.h> | |
| 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<std::string> | |
| StudyName("study-name", cl::desc("The name for this study"), cl::Required); | |
| static cl::opt<std::string> | |
| SizeDistributionName("size-distribution-name", | |
| cl::desc("The name of the distribution to use")); | |
| static cl::opt<bool> SweepMode( | |
| "sweep-mode", | |
| cl::desc( | |
| "If set, benchmark all sizes from sweep-min-size to sweep-max-size")); | |
| static cl::opt<uint32_t> | |
| SweepMinSize("sweep-min-size", | |
| cl::desc("The minimum size to use in sweep-mode"), | |
| cl::init(0)); | |
| static cl::opt<uint32_t> | |
| SweepMaxSize("sweep-max-size", | |
| cl::desc("The maximum size to use in sweep-mode"), | |
| cl::init(256)); | |
| static cl::opt<uint32_t> | |
| 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<std::string> Output("output", | |
| cl::desc("Specify output filename"), | |
| cl::value_desc("filename"), cl::init("-")); | |
| static cl::opt<uint32_t> | |
| 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<ParameterBatch::ParameterType> | |
| 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<Duration> &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<Duration> &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<double> probabilities; | |
| std::discrete_distribution<unsigned> 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<MemfunctionBenchmarkBase> 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; | |
| } |