Update benchmark to allow it to be built in runtimes driven mode. It is still useful to have an in-tree benchmark suite. On my side, I am working on some allocators related benchmarks. Due to the broken state of this in-tree benchmark suite, I have to frequently port changes between two workspace. Also, having benchmark suite can make it easier to track performance change in a consistent view. Assisted-by: AI tools, checked manually
290 lines
9.3 KiB
C++
290 lines
9.3 KiB
C++
//===-- 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;
|
|
}
|