|  | //===-- 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> | 
|  | generateBatch(size_t Iterations) { | 
|  | randomize(); | 
|  | return cycle(ArrayRef(Parameters), Iterations); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | Study createStudy() { | 
|  | Study Study; | 
|  | // Setup study. | 
|  | Study.StudyName = StudyName; | 
|  | Runtime &RI = Study.Runtime; | 
|  | RI.Host = HostState::get(); | 
|  | RI.BufferSize = BufferSize; | 
|  | RI.BatchParameterCount = BatchSize; | 
|  |  | 
|  | BenchmarkOptions &BO = RI.BenchmarkOptions; | 
|  | BO.MinDuration = std::chrono::milliseconds(1); | 
|  | BO.MaxDuration = std::chrono::seconds(1); | 
|  | BO.MaxIterations = 10'000'000U; | 
|  | BO.MinSamples = 4; | 
|  | BO.MaxSamples = 1000; | 
|  | BO.Epsilon = 0.01; // 1% | 
|  | BO.ScalingFactor = 1.4; | 
|  |  | 
|  | StudyConfiguration &SC = Study.Configuration; | 
|  | SC.NumTrials = NumTrials; | 
|  | SC.IsSweepMode = SweepMode; | 
|  | SC.AccessAlignment = 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.BestGuess); | 
|  | 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() | 
|  | : OffsetSampler(MemfunctionBenchmarkBase::BufferSize, SweepMaxSize, | 
|  | MaybeAlign(AlignedAccess)) {} | 
|  |  | 
|  | virtual void randomize() override { | 
|  | for (auto &P : Parameters) { | 
|  | P.OffsetBytes = OffsetSampler(Gen); | 
|  | P.SizeBytes = CurrentSweepSize; | 
|  | checkValid(P); | 
|  | } | 
|  | } | 
|  |  | 
|  | virtual Study run() override { | 
|  | Study Study = createStudy(); | 
|  | Study.Configuration.SweepModeMaxSize = SweepMaxSize; | 
|  | BenchmarkOptions &BO = Study.Runtime.BenchmarkOptions; | 
|  | BO.MinDuration = std::chrono::milliseconds(1); | 
|  | BO.InitialIterations = 100; | 
|  | auto &Measurements = Study.Measurements; | 
|  | Measurements.reserve(NumTrials * SweepMaxSize); | 
|  | for (size_t Size = SweepMinSize; Size <= SweepMaxSize; ++Size) { | 
|  | CurrentSweepSize = Size; | 
|  | runTrials(BO, Measurements); | 
|  | } | 
|  | return Study; | 
|  | } | 
|  |  | 
|  | private: | 
|  | size_t CurrentSweepSize = 0; | 
|  | OffsetDistribution OffsetSampler; | 
|  | std::mt19937_64 Gen; | 
|  | }; | 
|  |  | 
|  | struct MemfunctionBenchmarkDistribution final | 
|  | : public MemfunctionBenchmarkBase { | 
|  | MemfunctionBenchmarkDistribution(MemorySizeDistribution Distribution) | 
|  | : Distribution(Distribution), Probabilities(Distribution.Probabilities), | 
|  | SizeSampler(Probabilities.begin(), Probabilities.end()), | 
|  | OffsetSampler(MemfunctionBenchmarkBase::BufferSize, | 
|  | Probabilities.size() - 1, MaybeAlign(AlignedAccess)) {} | 
|  |  | 
|  | virtual void randomize() override { | 
|  | for (auto &P : Parameters) { | 
|  | P.OffsetBytes = OffsetSampler(Gen); | 
|  | P.SizeBytes = SizeSampler(Gen); | 
|  | checkValid(P); | 
|  | } | 
|  | } | 
|  |  | 
|  | virtual Study run() override { | 
|  | Study Study = createStudy(); | 
|  | Study.Configuration.SizeDistributionName = Distribution.Name.str(); | 
|  | BenchmarkOptions &BO = Study.Runtime.BenchmarkOptions; | 
|  | BO.MinDuration = std::chrono::milliseconds(10); | 
|  | BO.InitialIterations = BatchSize * 10; | 
|  | auto &Measurements = Study.Measurements; | 
|  | Measurements.reserve(NumTrials); | 
|  | runTrials(BO, Measurements); | 
|  | return Study; | 
|  | } | 
|  |  | 
|  | private: | 
|  | MemorySizeDistribution Distribution; | 
|  | ArrayRef<double> Probabilities; | 
|  | std::discrete_distribution<unsigned> SizeSampler; | 
|  | OffsetDistribution OffsetSampler; | 
|  | 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::getDistributions(), 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; | 
|  | } |