[libc][benchmark] allow benchmark to be built without llvm support (#200951)

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
This commit is contained in:
Schrodinger ZHU Yifan 2026-06-25 12:16:08 -07:00 committed by GitHub
parent aa939e521d
commit aca3f14c0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 674 additions and 518 deletions

View file

@ -10,16 +10,28 @@ endif()
find_package(Threads)
set(LLVM_LINK_COMPONENTS
Support
TargetParser
)
option(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT "Avoid LLVM Support dependency and reporting CPU details" ON)
if(NOT TARGET LLVMSupport OR NOT TARGET LLVMTargetParser)
set(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT OFF CACHE BOOL "Avoid LLVM Support dependency and reporting CPU details" FORCE)
endif()
if(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT)
set(LLVM_LINK_COMPONENTS
Support
TargetParser
)
endif()
#==============================================================================
# Add Unit Testing Support
#==============================================================================
make_gtest_target()
if(COMMAND build_gtest)
build_gtest()
elseif(COMMAND make_gtest_target)
make_gtest_target()
endif()
function(add_libc_benchmark_unittest target_name)
if(NOT LLVM_INCLUDE_TESTS)
@ -64,6 +76,9 @@ ExternalProject_Add(google-benchmark-libc
PREFIX google-benchmark-libc
SOURCE_DIR ${LLVM_THIRD_PARTY_DIR}/benchmark
INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc
BUILD_BYPRODUCTS
${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/lib/libbenchmark.a
${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/lib/libbenchmark_main.a
CMAKE_CACHE_ARGS
-DBENCHMARK_ENABLE_EXCEPTIONS:BOOL=OFF
-DBENCHMARK_ENABLE_LTO:BOOL=OFF
@ -87,6 +102,26 @@ ExternalProject_Add(google-benchmark-libc
-DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/include")
if(NOT TARGET benchmark::benchmark)
add_library(benchmark::benchmark STATIC IMPORTED)
set_target_properties(benchmark::benchmark PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/lib/libbenchmark.a"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/include"
)
add_dependencies(benchmark::benchmark google-benchmark-libc)
endif()
if(NOT TARGET benchmark_main)
add_library(benchmark_main STATIC IMPORTED)
set_target_properties(benchmark_main PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/lib/libbenchmark_main.a"
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/google-benchmark-libc/include"
)
add_dependencies(benchmark_main google-benchmark-libc)
endif()
add_custom_target(libc-benchmark-util-tests)
# libc-benchmark
@ -103,10 +138,18 @@ target_include_directories(libc-benchmark
target_link_libraries(libc-benchmark
PUBLIC
benchmark::benchmark
LLVMSupport
LLVMTargetParser
Threads::Threads
)
if(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT)
target_link_libraries(libc-benchmark
PUBLIC
LLVMSupport
LLVMTargetParser
)
target_compile_definitions(libc-benchmark PUBLIC "LIBC_BENCHMARKS_HAS_LLVM_SUPPORT")
else()
target_compile_definitions(libc-benchmark PUBLIC "LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING=1")
endif()
add_dependencies(libc-benchmark google-benchmark-libc)
llvm_update_compile_flags(libc-benchmark)
@ -141,54 +184,56 @@ add_libc_benchmark_unittest(libc-memory-benchmark-test
DEPENDS libc-memory-benchmark
)
# json
add_library(json
STATIC
EXCLUDE_FROM_ALL
JSON.cpp
JSON.h
)
target_link_libraries(json PUBLIC libc-memory-benchmark)
llvm_update_compile_flags(json)
if(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT)
# json
add_library(json
STATIC
EXCLUDE_FROM_ALL
JSON.cpp
JSON.h
)
target_link_libraries(json PUBLIC libc-memory-benchmark)
llvm_update_compile_flags(json)
add_libc_benchmark_unittest(json-test
SRCS JSONTest.cpp
DEPENDS json
)
add_libc_benchmark_unittest(json-test
SRCS JSONTest.cpp
DEPENDS json
)
#==============================================================================
# Benchmarking tool
#==============================================================================
#==============================================================================
# Benchmarking tool
#==============================================================================
# Benchmark all implementations that can run on the target CPU.
function(add_libc_multi_impl_benchmark name)
get_property(fq_implementations GLOBAL PROPERTY ${name}_implementations)
foreach(fq_config_name IN LISTS fq_implementations)
get_target_property(required_cpu_features ${fq_config_name} REQUIRE_CPU_FEATURES)
cpu_supports(can_run "${required_cpu_features}")
if(can_run)
set(benchmark_name ${fq_config_name}_benchmark)
add_executable(${benchmark_name}
EXCLUDE_FROM_ALL
LibcMemoryBenchmarkMain.cpp
)
get_target_property(entrypoint_object_file ${fq_config_name} "OBJECT_FILE_RAW")
target_link_libraries(${benchmark_name} PUBLIC json ${entrypoint_object_file})
string(TOUPPER ${name} name_upper)
target_compile_definitions(${benchmark_name} PRIVATE "-DLIBC_BENCHMARK_FUNCTION_${name_upper}=LIBC_NAMESPACE::${name}" "-DLIBC_BENCHMARK_FUNCTION_NAME=\"${fq_config_name}\"")
llvm_update_compile_flags(${benchmark_name})
else()
message(STATUS "Skipping benchmark for '${fq_config_name}' insufficient host cpu features '${required_cpu_features}'")
endif()
endforeach()
endfunction()
# Benchmark all implementations that can run on the target CPU.
function(add_libc_multi_impl_benchmark name)
get_property(fq_implementations GLOBAL PROPERTY ${name}_implementations)
foreach(fq_config_name IN LISTS fq_implementations)
get_target_property(required_cpu_features ${fq_config_name} REQUIRE_CPU_FEATURES)
cpu_supports(can_run "${required_cpu_features}")
if(can_run)
set(benchmark_name ${fq_config_name}_benchmark)
add_executable(${benchmark_name}
EXCLUDE_FROM_ALL
LibcMemoryBenchmarkMain.cpp
)
get_target_property(entrypoint_object_file ${fq_config_name} "OBJECT_FILE_RAW")
target_link_libraries(${benchmark_name} PUBLIC json ${entrypoint_object_file})
string(TOUPPER ${name} name_upper)
target_compile_definitions(${benchmark_name} PRIVATE "-DLIBC_BENCHMARK_FUNCTION_${name_upper}=LIBC_NAMESPACE::${name}" "-DLIBC_BENCHMARK_FUNCTION_NAME=\"${fq_config_name}\"")
llvm_update_compile_flags(${benchmark_name})
else()
message(STATUS "Skipping benchmark for '${fq_config_name}' insufficient host cpu features '${required_cpu_features}'")
endif()
endforeach()
endfunction()
add_libc_multi_impl_benchmark(bcmp)
add_libc_multi_impl_benchmark(bzero)
add_libc_multi_impl_benchmark(memcmp)
add_libc_multi_impl_benchmark(memcpy)
add_libc_multi_impl_benchmark(memmove)
add_libc_multi_impl_benchmark(memset)
add_libc_multi_impl_benchmark(bcmp)
add_libc_multi_impl_benchmark(bzero)
add_libc_multi_impl_benchmark(memcmp)
add_libc_multi_impl_benchmark(memcpy)
add_libc_multi_impl_benchmark(memmove)
add_libc_multi_impl_benchmark(memset)
endif()
#==============================================================================
# Google Benchmarking tool
@ -214,6 +259,7 @@ target_link_libraries(libc.benchmarks.memory_functions.opt_host
benchmark_main
)
llvm_update_compile_flags(libc.benchmarks.memory_functions.opt_host)
add_dependencies(libc.benchmarks.memory_functions.opt_host google-benchmark-libc)
if(LIBC_TYPES_HAS_FLOAT16)
add_executable(libc.benchmarks.rsqrtf16.opt_host
@ -234,4 +280,3 @@ if(LIBC_TYPES_HAS_FLOAT16)
)
llvm_update_compile_flags(libc.benchmarks.rsqrtf16.opt_host)
endif()

View file

@ -174,63 +174,63 @@ public:
static Error fromJson(const json::Value &V,
libc_benchmarks::BenchmarkOptions &Out) {
JsonObjectMapper O(V);
O.map("MinDuration", Out.MinDuration);
O.map("MaxDuration", Out.MaxDuration);
O.map("InitialIterations", Out.InitialIterations);
O.map("MaxIterations", Out.MaxIterations);
O.map("MinSamples", Out.MinSamples);
O.map("MaxSamples", Out.MaxSamples);
O.map("Epsilon", Out.Epsilon);
O.map("ScalingFactor", Out.ScalingFactor);
O.map("Log", Out.Log);
O.map("MinDuration", Out.min_duration);
O.map("MaxDuration", Out.max_duration);
O.map("InitialIterations", Out.initial_iterations);
O.map("MaxIterations", Out.max_iterations);
O.map("MinSamples", Out.min_samples);
O.map("MaxSamples", Out.max_samples);
O.map("Epsilon", Out.epsilon);
O.map("ScalingFactor", Out.scaling_factor);
O.map("Log", Out.log);
return O.takeError();
}
static Error fromJson(const json::Value &V,
libc_benchmarks::StudyConfiguration &Out) {
JsonObjectMapper O(V);
O.map("Function", Out.Function);
O.map("NumTrials", Out.NumTrials);
O.map("IsSweepMode", Out.IsSweepMode);
O.map("SweepModeMaxSize", Out.SweepModeMaxSize);
O.map("SizeDistributionName", Out.SizeDistributionName);
O.map("AccessAlignment", Out.AccessAlignment);
O.map("MemcmpMismatchAt", Out.MemcmpMismatchAt);
O.map("Function", Out.function);
O.map("NumTrials", Out.num_trials);
O.map("IsSweepMode", Out.is_sweep_mode);
O.map("SweepModeMaxSize", Out.sweep_mode_max_size);
O.map("SizeDistributionName", Out.size_distribution_name);
O.map("AccessAlignment", Out.access_alignment);
O.map("MemcmpMismatchAt", Out.memcmp_mismatch_at);
return O.takeError();
}
static Error fromJson(const json::Value &V, libc_benchmarks::CacheInfo &Out) {
JsonObjectMapper O(V);
O.map("Type", Out.Type);
O.map("Level", Out.Level);
O.map("Size", Out.Size);
O.map("NumSharing", Out.NumSharing);
O.map("Type", Out.type);
O.map("Level", Out.level);
O.map("Size", Out.size);
O.map("NumSharing", Out.num_sharing);
return O.takeError();
}
static Error fromJson(const json::Value &V, libc_benchmarks::HostState &Out) {
JsonObjectMapper O(V);
O.map("CpuName", Out.CpuName);
O.map("CpuFrequency", Out.CpuFrequency);
O.map("Caches", Out.Caches);
O.map("CpuName", Out.cpu_name);
O.map("CpuFrequency", Out.cpu_frequency);
O.map("Caches", Out.caches);
return O.takeError();
}
static Error fromJson(const json::Value &V, libc_benchmarks::Runtime &Out) {
JsonObjectMapper O(V);
O.map("Host", Out.Host);
O.map("BufferSize", Out.BufferSize);
O.map("BatchParameterCount", Out.BatchParameterCount);
O.map("BenchmarkOptions", Out.BenchmarkOptions);
O.map("Host", Out.host);
O.map("BufferSize", Out.buffer_size);
O.map("BatchParameterCount", Out.batch_parameter_count);
O.map("BenchmarkOptions", Out.benchmark_options);
return O.takeError();
}
static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) {
JsonObjectMapper O(V);
O.map("StudyName", Out.StudyName);
O.map("Runtime", Out.Runtime);
O.map("Configuration", Out.Configuration);
O.map("Measurements", Out.Measurements);
O.map("StudyName", Out.study_name);
O.map("Runtime", Out.runtime);
O.map("Configuration", Out.configuration);
O.map("Measurements", Out.measurements);
return O.takeError();
}
@ -261,61 +261,61 @@ static StringRef serialize(const BenchmarkLog &L) {
}
static void serialize(const BenchmarkOptions &BO, json::OStream &JOS) {
JOS.attribute("MinDuration", seconds(BO.MinDuration));
JOS.attribute("MaxDuration", seconds(BO.MaxDuration));
JOS.attribute("InitialIterations", BO.InitialIterations);
JOS.attribute("MaxIterations", BO.MaxIterations);
JOS.attribute("MinSamples", BO.MinSamples);
JOS.attribute("MaxSamples", BO.MaxSamples);
JOS.attribute("Epsilon", BO.Epsilon);
JOS.attribute("ScalingFactor", BO.ScalingFactor);
JOS.attribute("Log", serialize(BO.Log));
JOS.attribute("MinDuration", seconds(BO.min_duration));
JOS.attribute("MaxDuration", seconds(BO.max_duration));
JOS.attribute("InitialIterations", BO.initial_iterations);
JOS.attribute("MaxIterations", BO.max_iterations);
JOS.attribute("MinSamples", BO.min_samples);
JOS.attribute("MaxSamples", BO.max_samples);
JOS.attribute("Epsilon", BO.epsilon);
JOS.attribute("ScalingFactor", BO.scaling_factor);
JOS.attribute("Log", serialize(BO.log));
}
static void serialize(const CacheInfo &CI, json::OStream &JOS) {
JOS.attribute("Type", CI.Type);
JOS.attribute("Level", CI.Level);
JOS.attribute("Size", CI.Size);
JOS.attribute("NumSharing", CI.NumSharing);
JOS.attribute("Type", CI.type);
JOS.attribute("Level", CI.level);
JOS.attribute("Size", CI.size);
JOS.attribute("NumSharing", CI.num_sharing);
}
static void serialize(const StudyConfiguration &SC, json::OStream &JOS) {
JOS.attribute("Function", SC.Function);
JOS.attribute("NumTrials", SC.NumTrials);
JOS.attribute("IsSweepMode", SC.IsSweepMode);
JOS.attribute("SweepModeMaxSize", SC.SweepModeMaxSize);
JOS.attribute("SizeDistributionName", SC.SizeDistributionName);
JOS.attribute("Function", SC.function);
JOS.attribute("NumTrials", SC.num_trials);
JOS.attribute("IsSweepMode", SC.is_sweep_mode);
JOS.attribute("SweepModeMaxSize", SC.sweep_mode_max_size);
JOS.attribute("SizeDistributionName", SC.size_distribution_name);
JOS.attribute("AccessAlignment",
static_cast<int64_t>(SC.AccessAlignment->value()));
JOS.attribute("MemcmpMismatchAt", SC.MemcmpMismatchAt);
static_cast<int64_t>(SC.access_alignment->value()));
JOS.attribute("MemcmpMismatchAt", SC.memcmp_mismatch_at);
}
static void serialize(const HostState &HS, json::OStream &JOS) {
JOS.attribute("CpuName", HS.CpuName);
JOS.attribute("CpuFrequency", HS.CpuFrequency);
JOS.attribute("CpuName", HS.cpu_name);
JOS.attribute("CpuFrequency", HS.cpu_frequency);
JOS.attributeArray("Caches", [&]() {
for (const auto &CI : HS.Caches)
for (const auto &CI : HS.caches)
JOS.object([&]() { serialize(CI, JOS); });
});
}
static void serialize(const Runtime &RI, json::OStream &JOS) {
JOS.attributeObject("Host", [&]() { serialize(RI.Host, JOS); });
JOS.attribute("BufferSize", RI.BufferSize);
JOS.attribute("BatchParameterCount", RI.BatchParameterCount);
JOS.attributeObject("Host", [&]() { serialize(RI.host, JOS); });
JOS.attribute("BufferSize", RI.buffer_size);
JOS.attribute("BatchParameterCount", RI.batch_parameter_count);
JOS.attributeObject("BenchmarkOptions",
[&]() { serialize(RI.BenchmarkOptions, JOS); });
[&]() { serialize(RI.benchmark_options, JOS); });
}
void serializeToJson(const Study &S, json::OStream &JOS) {
JOS.object([&]() {
JOS.attribute("StudyName", S.StudyName);
JOS.attributeObject("Runtime", [&]() { serialize(S.Runtime, JOS); });
JOS.attribute("StudyName", S.study_name);
JOS.attributeObject("Runtime", [&]() { serialize(S.runtime, JOS); });
JOS.attributeObject("Configuration",
[&]() { serialize(S.Configuration, JOS); });
if (!S.Measurements.empty()) {
[&]() { serialize(S.configuration, JOS); });
if (!S.measurements.empty()) {
JOS.attributeArray("Measurements", [&]() {
for (const auto &M : S.Measurements)
for (const auto &M : S.measurements)
JOS.value(seconds(M));
});
}

View file

@ -48,64 +48,67 @@ static std::string serializeToString(const Study &S) {
MATCHER(EqualsCacheInfo, "") {
const CacheInfo &A = ::testing::get<0>(arg);
const CacheInfo &B = ::testing::get<1>(arg);
return ExplainMatchResult(AllOf(Field(&CacheInfo::Type, B.Type),
Field(&CacheInfo::Level, B.Level),
Field(&CacheInfo::Size, B.Size),
Field(&CacheInfo::NumSharing, B.NumSharing)),
A, result_listener);
return ExplainMatchResult(
AllOf(Field(&CacheInfo::type, B.type), Field(&CacheInfo::level, B.level),
Field(&CacheInfo::size, B.size),
Field(&CacheInfo::num_sharing, B.num_sharing)),
A, result_listener);
}
auto equals(const HostState &H) -> auto {
return AllOf(
Field(&HostState::CpuName, H.CpuName),
Field(&HostState::CpuFrequency, H.CpuFrequency),
Field(&HostState::Caches, Pointwise(EqualsCacheInfo(), H.Caches)));
Field(&HostState::cpu_name, H.cpu_name),
Field(&HostState::cpu_frequency, H.cpu_frequency),
Field(&HostState::caches, Pointwise(EqualsCacheInfo(), H.caches)));
}
auto equals(const StudyConfiguration &SC) -> auto {
return AllOf(
Field(&StudyConfiguration::Function, SC.Function),
Field(&StudyConfiguration::NumTrials, SC.NumTrials),
Field(&StudyConfiguration::IsSweepMode, SC.IsSweepMode),
Field(&StudyConfiguration::SweepModeMaxSize, SC.SweepModeMaxSize),
Field(&StudyConfiguration::SizeDistributionName, SC.SizeDistributionName),
Field(&StudyConfiguration::AccessAlignment, SC.AccessAlignment),
Field(&StudyConfiguration::MemcmpMismatchAt, SC.MemcmpMismatchAt));
Field(&StudyConfiguration::function, SC.function),
Field(&StudyConfiguration::num_trials, SC.num_trials),
Field(&StudyConfiguration::is_sweep_mode, SC.is_sweep_mode),
Field(&StudyConfiguration::sweep_mode_max_size, SC.sweep_mode_max_size),
Field(&StudyConfiguration::size_distribution_name,
SC.size_distribution_name),
Field(&StudyConfiguration::access_alignment, SC.access_alignment),
Field(&StudyConfiguration::memcmp_mismatch_at, SC.memcmp_mismatch_at));
}
auto equals(const BenchmarkOptions &BO) -> auto {
return AllOf(
Field(&BenchmarkOptions::MinDuration, BO.MinDuration),
Field(&BenchmarkOptions::MaxDuration, BO.MaxDuration),
Field(&BenchmarkOptions::InitialIterations, BO.InitialIterations),
Field(&BenchmarkOptions::MaxIterations, BO.MaxIterations),
Field(&BenchmarkOptions::MinSamples, BO.MinSamples),
Field(&BenchmarkOptions::MaxSamples, BO.MaxSamples),
Field(&BenchmarkOptions::Epsilon, BO.Epsilon),
Field(&BenchmarkOptions::ScalingFactor, BO.ScalingFactor),
Field(&BenchmarkOptions::Log, BO.Log));
Field(&BenchmarkOptions::min_duration, BO.min_duration),
Field(&BenchmarkOptions::max_duration, BO.max_duration),
Field(&BenchmarkOptions::initial_iterations, BO.initial_iterations),
Field(&BenchmarkOptions::max_iterations, BO.max_iterations),
Field(&BenchmarkOptions::min_samples, BO.min_samples),
Field(&BenchmarkOptions::max_samples, BO.max_samples),
Field(&BenchmarkOptions::epsilon, BO.epsilon),
Field(&BenchmarkOptions::scaling_factor, BO.scaling_factor),
Field(&BenchmarkOptions::log, BO.log));
}
auto equals(const Runtime &RI) -> auto {
return AllOf(Field(&Runtime::Host, equals(RI.Host)),
Field(&Runtime::BufferSize, RI.BufferSize),
Field(&Runtime::BatchParameterCount, RI.BatchParameterCount),
Field(&Runtime::BenchmarkOptions, equals(RI.BenchmarkOptions)));
return AllOf(
Field(&Runtime::host, equals(RI.host)),
Field(&Runtime::buffer_size, RI.buffer_size),
Field(&Runtime::batch_parameter_count, RI.batch_parameter_count),
Field(&Runtime::benchmark_options, equals(RI.benchmark_options)));
}
auto equals(const Study &S) -> auto {
return AllOf(Field(&Study::StudyName, S.StudyName),
Field(&Study::Runtime, equals(S.Runtime)),
Field(&Study::Configuration, equals(S.Configuration)),
Field(&Study::Measurements, S.Measurements));
return AllOf(Field(&Study::study_name, S.study_name),
Field(&Study::runtime, equals(S.runtime)),
Field(&Study::configuration, equals(S.configuration)),
Field(&Study::measurements, S.measurements));
}
TEST(JsonTest, RoundTrip) {
const Study S = getStudy();
const auto Serialized = serializeToString(S);
auto StudyOrError = parseJsonStudy(Serialized);
if (auto Err = StudyOrError.takeError())
if (auto Err = StudyOrError.takeError()) {
EXPECT_FALSE(Err) << "Unexpected error : " << Err << "\n" << Serialized;
}
const Study &Parsed = *StudyOrError;
EXPECT_THAT(Parsed, equals(S)) << Serialized << "\n"
<< serializeToString(Parsed);

View file

@ -8,7 +8,9 @@
#include "LibcBenchmark.h"
#include "llvm/ADT/StringRef.h"
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
#include "llvm/TargetParser/Host.h"
#endif
namespace llvm {
namespace libc_benchmarks {
@ -22,20 +24,58 @@ void checkRequirements() {
}
HostState HostState::get() {
const auto &CpuInfo = benchmark::CPUInfo::Get();
HostState H;
H.CpuFrequency = CpuInfo.cycles_per_second;
H.CpuName = llvm::sys::getHostCPUName().str();
for (const auto &BenchmarkCacheInfo : CpuInfo.caches) {
CacheInfo CI;
CI.Type = BenchmarkCacheInfo.type;
CI.Level = BenchmarkCacheInfo.level;
CI.Size = BenchmarkCacheInfo.size;
CI.NumSharing = BenchmarkCacheInfo.num_sharing;
H.Caches.push_back(std::move(CI));
const auto &cpu_info = benchmark::CPUInfo::Get();
HostState h;
h.cpu_frequency = cpu_info.cycles_per_second;
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
h.cpu_name = llvm::sys::getHostCPUName().str();
#else
h.cpu_name = "";
#endif
for (const auto &benchmark_cache_info : cpu_info.caches) {
CacheInfo ci;
ci.type = benchmark_cache_info.type;
ci.level = benchmark_cache_info.level;
ci.size = benchmark_cache_info.size;
ci.num_sharing = benchmark_cache_info.num_sharing;
h.caches.push_back(std::move(ci));
}
return H;
return h;
}
} // namespace libc_benchmarks
} // namespace llvm
#ifndef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
#include "llvm/ADT/Twine.h"
#include <cstdlib>
#include <iostream>
#include <string_view>
#if __has_include(<execinfo.h>)
#include <execinfo.h>
#include <unistd.h>
#endif
namespace llvm {
[[noreturn]] void report_fatal_error(std::string_view reason,
bool gen_crash_diag) {
std::cerr << "Fatal error: " << reason << std::endl;
#if __has_include(<execinfo.h>)
if (gen_crash_diag) {
void *buffer[64]{};
int nptrs = ::backtrace(buffer, 64);
::backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO);
}
#endif
std::abort();
}
[[noreturn]] void report_fatal_error(StringRef reason, bool gen_crash_diag) {
report_fatal_error(std::string_view(reason.data(), reason.size()),
gen_crash_diag);
}
[[noreturn]] void report_fatal_error(const Twine &reason, bool gen_crash_diag) {
report_fatal_error("(unknown twine error)", gen_crash_diag);
}
} // namespace llvm
#endif

View file

@ -39,6 +39,8 @@
#include <cstdint>
#include <optional>
#include "llvm/Support/ErrorHandling.h"
namespace llvm {
namespace libc_benchmarks {
@ -55,24 +57,24 @@ enum class BenchmarkLog {
// meaning of each field.
struct BenchmarkOptions {
// The minimum time for which the benchmark is running.
Duration MinDuration = std::chrono::seconds(0);
Duration min_duration = std::chrono::seconds(0);
// The maximum time for which the benchmark is running.
Duration MaxDuration = std::chrono::seconds(10);
Duration max_duration = std::chrono::seconds(10);
// The number of iterations in the first sample.
uint32_t InitialIterations = 1;
uint32_t initial_iterations = 1;
// The maximum number of iterations for any given sample.
uint32_t MaxIterations = 10000000;
uint32_t max_iterations = 10000000;
// The minimum number of samples.
uint32_t MinSamples = 4;
uint32_t min_samples = 4;
// The maximum number of samples.
uint32_t MaxSamples = 1000;
uint32_t max_samples = 1000;
// The benchmark will stop if the relative difference between the current and
// the last estimation is less than epsilon. This is 1% by default.
double Epsilon = 0.01;
double epsilon = 0.01;
// The number of iterations grows exponentially between each sample.
// Must be greater or equal to 1.
double ScalingFactor = 1.4;
BenchmarkLog Log = BenchmarkLog::None;
double scaling_factor = 1.4;
BenchmarkLog log = BenchmarkLog::None;
};
// The state of a benchmark.
@ -87,35 +89,42 @@ enum class BenchmarkStatus {
// The internal state of the benchmark, useful to debug, test or report
// statistics.
struct BenchmarkState {
size_t LastSampleIterations;
Duration LastBatchElapsed;
BenchmarkStatus CurrentStatus;
Duration CurrentBestGuess; // The time estimation for a single run of `foo`.
double ChangeRatio; // The change in time estimation between previous and
// current samples.
size_t last_sample_iterations;
Duration last_batch_elapsed;
BenchmarkStatus current_status;
Duration current_best_guess; // The time estimation for a single run of `foo`.
double change_ratio; // The change in time estimation between previous and
// current samples.
};
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
using BenchmarkLogType = llvm::SmallVector<BenchmarkState, 16>;
#else
#include <vector>
using BenchmarkLogType = std::vector<BenchmarkState>;
#endif
// A lightweight result for a benchmark.
struct BenchmarkResult {
BenchmarkStatus TerminationStatus = BenchmarkStatus::Running;
Duration BestGuess = {};
std::optional<llvm::SmallVector<BenchmarkState, 16>> MaybeBenchmarkLog;
BenchmarkStatus termination_status = BenchmarkStatus::Running;
Duration best_guess = {};
std::optional<BenchmarkLogType> maybe_benchmark_log;
};
// Stores information about a cache in the host memory system.
struct CacheInfo {
std::string Type; // e.g. "Instruction", "Data", "Unified".
int Level; // 0 is closest to processing unit.
int Size; // In bytes.
int NumSharing; // The number of processing units (Hyper-Threading Thread)
std::string type; // e.g. "Instruction", "Data", "Unified".
int level; // 0 is closest to processing unit.
int size; // In bytes.
int num_sharing; // The number of processing units (Hyper-Threading Thread)
// with which this cache is shared.
};
// Stores information about the host.
struct HostState {
std::string CpuName; // returns a string compatible with the -march option.
double CpuFrequency; // in Hertz.
std::vector<CacheInfo> Caches;
std::string cpu_name; // returns a string compatible with the -march option.
double cpu_frequency; // in Hertz.
std::vector<CacheInfo> caches;
static HostState get();
};
@ -123,41 +132,41 @@ struct HostState {
namespace internal {
struct Measurement {
size_t Iterations = 0;
Duration Elapsed = {};
size_t iterations = 0;
Duration elapsed = {};
};
// Updates the estimation of the elapsed time for a single iteration.
class RefinableRuntimeEstimation {
Duration TotalTime = {};
size_t TotalIterations = 0;
Duration total_time = {};
size_t total_iterations = 0;
public:
Duration update(const Measurement &M) {
assert(M.Iterations > 0);
Duration update(const Measurement &m) {
assert(m.iterations > 0);
// Duration is encoded as a double (see definition).
// `TotalTime` and `M.Elapsed` are of the same magnitude so we don't expect
// `total_time` and `m.elapsed` are of the same magnitude so we don't expect
// loss of precision due to radically different scales.
TotalTime += M.Elapsed;
TotalIterations += M.Iterations;
return TotalTime / TotalIterations;
total_time += m.elapsed;
total_iterations += m.iterations;
return total_time / total_iterations;
}
};
// This class tracks the progression of the runtime estimation.
class RuntimeEstimationProgression {
RefinableRuntimeEstimation RRE;
RefinableRuntimeEstimation rre;
public:
Duration CurrentEstimation = {};
Duration current_estimation = {};
// Returns the change ratio between our best guess so far and the one from the
// new measurement.
double computeImprovement(const Measurement &M) {
const Duration NewEstimation = RRE.update(M);
const double Ratio = fabs(((CurrentEstimation / NewEstimation) - 1.0));
CurrentEstimation = NewEstimation;
return Ratio;
double compute_improvement(const Measurement &m) {
const Duration new_estimation = rre.update(m);
const double ratio = fabs(((current_estimation / new_estimation) - 1.0));
current_estimation = new_estimation;
return ratio;
}
};
@ -192,78 +201,78 @@ public:
// `std::chrono::time_point now();`
template <typename Function, typename ParameterProvider,
typename BenchmarkClock = const std::chrono::high_resolution_clock>
BenchmarkResult benchmark(const BenchmarkOptions &Options,
BenchmarkResult benchmark(const BenchmarkOptions &options,
ParameterProvider &PP, Function foo,
BenchmarkClock &Clock = BenchmarkClock()) {
BenchmarkResult Result;
internal::RuntimeEstimationProgression REP;
Duration TotalBenchmarkDuration = {};
size_t Iterations = std::max(Options.InitialIterations, uint32_t(1));
size_t Samples = 0;
if (Options.ScalingFactor < 1.0)
report_fatal_error("ScalingFactor should be >= 1");
if (Options.Log != BenchmarkLog::None)
Result.MaybeBenchmarkLog.emplace();
BenchmarkResult result;
internal::RuntimeEstimationProgression rep;
Duration total_benchmark_duration = {};
size_t iterations = std::max(options.initial_iterations, uint32_t(1));
size_t samples = 0;
if (options.scaling_factor < 1.0)
report_fatal_error("scaling_factor should be >= 1");
if (options.log != BenchmarkLog::None)
result.maybe_benchmark_log.emplace();
for (;;) {
// Request a new Batch of size `Iterations`.
const auto &Batch = PP.generateBatch(Iterations);
// Request a new Batch of size `iterations`.
const auto &batch = PP.generate_batch(iterations);
// Measuring this Batch.
const auto StartTime = Clock.now();
for (const auto Parameter : Batch) {
auto Production = foo(Parameter);
benchmark::DoNotOptimize(Production);
const auto start_time = Clock.now();
for (const auto parameter : batch) {
auto production = foo(parameter);
benchmark::DoNotOptimize(production);
}
const auto EndTime = Clock.now();
const Duration Elapsed = EndTime - StartTime;
const auto end_time = Clock.now();
const Duration elapsed = end_time - start_time;
// Updating statistics.
++Samples;
TotalBenchmarkDuration += Elapsed;
const double ChangeRatio = REP.computeImprovement({Iterations, Elapsed});
Result.BestGuess = REP.CurrentEstimation;
++samples;
total_benchmark_duration += elapsed;
const double change_ratio = rep.compute_improvement({iterations, elapsed});
result.best_guess = rep.current_estimation;
// Stopping condition.
if (TotalBenchmarkDuration >= Options.MinDuration &&
Samples >= Options.MinSamples && ChangeRatio < Options.Epsilon)
Result.TerminationStatus = BenchmarkStatus::PrecisionReached;
else if (Samples >= Options.MaxSamples)
Result.TerminationStatus = BenchmarkStatus::MaxSamplesReached;
else if (TotalBenchmarkDuration >= Options.MaxDuration)
Result.TerminationStatus = BenchmarkStatus::MaxDurationReached;
else if (Iterations >= Options.MaxIterations)
Result.TerminationStatus = BenchmarkStatus::MaxIterationsReached;
if (total_benchmark_duration >= options.min_duration &&
samples >= options.min_samples && change_ratio < options.epsilon)
result.termination_status = BenchmarkStatus::PrecisionReached;
else if (samples >= options.max_samples)
result.termination_status = BenchmarkStatus::MaxSamplesReached;
else if (total_benchmark_duration >= options.max_duration)
result.termination_status = BenchmarkStatus::MaxDurationReached;
else if (iterations >= options.max_iterations)
result.termination_status = BenchmarkStatus::MaxIterationsReached;
if (Result.MaybeBenchmarkLog) {
auto &BenchmarkLog = *Result.MaybeBenchmarkLog;
if (Options.Log == BenchmarkLog::Last && !BenchmarkLog.empty())
BenchmarkLog.pop_back();
BenchmarkState BS;
BS.LastSampleIterations = Iterations;
BS.LastBatchElapsed = Elapsed;
BS.CurrentStatus = Result.TerminationStatus;
BS.CurrentBestGuess = Result.BestGuess;
BS.ChangeRatio = ChangeRatio;
BenchmarkLog.push_back(BS);
if (result.maybe_benchmark_log) {
auto &benchmark_log = *result.maybe_benchmark_log;
if (options.log == BenchmarkLog::Last && !benchmark_log.empty())
benchmark_log.pop_back();
BenchmarkState bs;
bs.last_sample_iterations = iterations;
bs.last_batch_elapsed = elapsed;
bs.current_status = result.termination_status;
bs.current_best_guess = result.best_guess;
bs.change_ratio = change_ratio;
benchmark_log.push_back(bs);
}
if (Result.TerminationStatus != BenchmarkStatus::Running)
return Result;
if (result.termination_status != BenchmarkStatus::Running)
return result;
if (Options.ScalingFactor > 1 &&
Iterations * Options.ScalingFactor == Iterations)
report_fatal_error(
"`Iterations *= ScalingFactor` is idempotent, increase ScalingFactor "
"or InitialIterations.");
if (options.scaling_factor > 1 &&
iterations * options.scaling_factor == iterations)
report_fatal_error("`iterations *= scaling_factor` is idempotent, "
"increase scaling_factor "
"or initial_iterations.");
Iterations *= Options.ScalingFactor;
iterations *= options.scaling_factor;
}
}
// Interprets `Array` as a circular buffer of `Size` elements.
template <typename T> class CircularArrayRef {
llvm::ArrayRef<T> Array;
size_t Size;
llvm::ArrayRef<T> array;
size_t size;
public:
using value_type = T;
@ -274,38 +283,38 @@ public:
class const_iterator {
using iterator_category = std::input_iterator_tag;
llvm::ArrayRef<T> Array;
size_t Index;
size_t Offset;
llvm::ArrayRef<T> array;
size_t index;
size_t offset;
public:
explicit const_iterator(llvm::ArrayRef<T> Array, size_t Index = 0)
: Array(Array), Index(Index), Offset(Index % Array.size()) {}
explicit const_iterator(llvm::ArrayRef<T> array, size_t index = 0)
: array(array), index(index), offset(index % array.size()) {}
const_iterator &operator++() {
++Index;
++Offset;
if (Offset == Array.size())
Offset = 0;
++index;
++offset;
if (offset == array.size())
offset = 0;
return *this;
}
bool operator==(const_iterator Other) const { return Index == Other.Index; }
bool operator!=(const_iterator Other) const { return !(*this == Other); }
const T &operator*() const { return Array[Offset]; }
bool operator==(const_iterator other) const { return index == other.index; }
bool operator!=(const_iterator other) const { return !(*this == other); }
const T &operator*() const { return array[offset]; }
};
CircularArrayRef(llvm::ArrayRef<T> Array, size_t Size)
: Array(Array), Size(Size) {
assert(Array.size() > 0);
CircularArrayRef(llvm::ArrayRef<T> array, size_t size)
: array(array), size(size) {
assert(array.size() > 0);
}
const_iterator begin() const { return const_iterator(Array); }
const_iterator end() const { return const_iterator(Array, Size); }
const_iterator begin() const { return const_iterator(array); }
const_iterator end() const { return const_iterator(array, size); }
};
// A convenient helper to produce a CircularArrayRef from an ArrayRef.
template <typename T>
CircularArrayRef<T> cycle(llvm::ArrayRef<T> Array, size_t Size) {
return {Array, Size};
CircularArrayRef<T> cycle(llvm::ArrayRef<T> array, size_t size) {
return {array, size};
}
// Creates an std::array which storage size is constrained under `Bytes`.
@ -315,8 +324,8 @@ using ByteConstrainedArray = std::array<T, Bytes / sizeof(T)>;
// A convenient helper to produce a CircularArrayRef from a
// ByteConstrainedArray.
template <typename T, size_t N>
CircularArrayRef<T> cycle(const std::array<T, N> &Container, size_t Size) {
return {llvm::ArrayRef<T>(Container.cbegin(), Container.cend()), Size};
CircularArrayRef<T> cycle(const std::array<T, N> &container, size_t size) {
return {llvm::ArrayRef<T>(container.cbegin(), container.cend()), size};
}
// Makes sure the binary was compiled in release mode and that frequency

View file

@ -30,7 +30,7 @@ namespace {
// A simple parameter provider returning a zero initialized vector of size
// `iterations`.
struct DummyParameterProvider {
std::vector<char> generateBatch(size_t iterations) {
std::vector<char> generate_batch(size_t iterations) {
return std::vector<char>(iterations);
}
};
@ -50,16 +50,17 @@ public:
}
protected:
void SetUp() override { Options.Log = BenchmarkLog::Full; }
void SetUp() override { options.log = BenchmarkLog::Full; }
void TearDown() override {
// We make sure all the expected measurements were performed.
if (MaybeTimepoints)
if (MaybeTimepoints) {
EXPECT_THAT(*MaybeTimepoints, IsEmpty());
}
}
BenchmarkResult run() {
return benchmark(Options, ParameterProvider, DummyFunction, *this);
return benchmark(options, ParameterProvider, DummyFunction, *this);
}
void setMeasurements(llvm::ArrayRef<Duration> Durations) {
@ -73,7 +74,7 @@ protected:
}
}
BenchmarkOptions Options;
BenchmarkOptions options;
private:
DummyParameterProvider ParameterProvider;
@ -82,67 +83,67 @@ private:
};
TEST_F(LibcBenchmark, MaxSamplesReached) {
Options.MaxSamples = 1;
const auto Result = run();
EXPECT_THAT(Result.MaybeBenchmarkLog->size(), 1);
EXPECT_THAT(Result.TerminationStatus, BenchmarkStatus::MaxSamplesReached);
options.max_samples = 1;
const auto result = run();
EXPECT_THAT(result.maybe_benchmark_log->size(), 1);
EXPECT_THAT(result.termination_status, BenchmarkStatus::MaxSamplesReached);
}
TEST_F(LibcBenchmark, MaxDurationReached) {
Options.MaxDuration = nanoseconds(10);
options.max_duration = nanoseconds(10);
setMeasurements({nanoseconds(11)});
const auto Result = run();
EXPECT_THAT(Result.MaybeBenchmarkLog->size(), 1);
EXPECT_THAT(Result.TerminationStatus, BenchmarkStatus::MaxDurationReached);
const auto result = run();
EXPECT_THAT(result.maybe_benchmark_log->size(), 1);
EXPECT_THAT(result.termination_status, BenchmarkStatus::MaxDurationReached);
}
TEST_F(LibcBenchmark, MaxIterationsReached) {
Options.InitialIterations = 1;
Options.MaxIterations = 20;
Options.ScalingFactor = 2;
Options.Epsilon = 0; // unreachable.
const auto Result = run();
EXPECT_THAT(*Result.MaybeBenchmarkLog,
ElementsAre(Field(&BenchmarkState::LastSampleIterations, 1),
Field(&BenchmarkState::LastSampleIterations, 2),
Field(&BenchmarkState::LastSampleIterations, 4),
Field(&BenchmarkState::LastSampleIterations, 8),
Field(&BenchmarkState::LastSampleIterations, 16),
Field(&BenchmarkState::LastSampleIterations, 32)));
EXPECT_THAT(Result.MaybeBenchmarkLog->size(), 6);
EXPECT_THAT(Result.TerminationStatus, BenchmarkStatus::MaxIterationsReached);
options.initial_iterations = 1;
options.max_iterations = 20;
options.scaling_factor = 2;
options.epsilon = 0; // unreachable.
const auto result = run();
EXPECT_THAT(*result.maybe_benchmark_log,
ElementsAre(Field(&BenchmarkState::last_sample_iterations, 1),
Field(&BenchmarkState::last_sample_iterations, 2),
Field(&BenchmarkState::last_sample_iterations, 4),
Field(&BenchmarkState::last_sample_iterations, 8),
Field(&BenchmarkState::last_sample_iterations, 16),
Field(&BenchmarkState::last_sample_iterations, 32)));
EXPECT_THAT(result.maybe_benchmark_log->size(), 6);
EXPECT_THAT(result.termination_status, BenchmarkStatus::MaxIterationsReached);
}
TEST_F(LibcBenchmark, MinSamples) {
Options.MinSamples = 4;
Options.ScalingFactor = 2;
Options.Epsilon = std::numeric_limits<double>::max(); // always reachable.
options.min_samples = 4;
options.scaling_factor = 2;
options.epsilon = std::numeric_limits<double>::max(); // always reachable.
setMeasurements(
{nanoseconds(1), nanoseconds(2), nanoseconds(4), nanoseconds(8)});
const auto Result = run();
EXPECT_THAT(*Result.MaybeBenchmarkLog,
ElementsAre(Field(&BenchmarkState::LastSampleIterations, 1),
Field(&BenchmarkState::LastSampleIterations, 2),
Field(&BenchmarkState::LastSampleIterations, 4),
Field(&BenchmarkState::LastSampleIterations, 8)));
EXPECT_THAT(Result.MaybeBenchmarkLog->size(), 4);
EXPECT_THAT(Result.TerminationStatus, BenchmarkStatus::PrecisionReached);
const auto result = run();
EXPECT_THAT(*result.maybe_benchmark_log,
ElementsAre(Field(&BenchmarkState::last_sample_iterations, 1),
Field(&BenchmarkState::last_sample_iterations, 2),
Field(&BenchmarkState::last_sample_iterations, 4),
Field(&BenchmarkState::last_sample_iterations, 8)));
EXPECT_THAT(result.maybe_benchmark_log->size(), 4);
EXPECT_THAT(result.termination_status, BenchmarkStatus::PrecisionReached);
}
TEST_F(LibcBenchmark, Epsilon) {
Options.MinSamples = 4;
Options.ScalingFactor = 2;
Options.Epsilon = std::numeric_limits<double>::max(); // always reachable.
options.min_samples = 4;
options.scaling_factor = 2;
options.epsilon = std::numeric_limits<double>::max(); // always reachable.
setMeasurements(
{nanoseconds(1), nanoseconds(2), nanoseconds(4), nanoseconds(8)});
const auto Result = run();
EXPECT_THAT(*Result.MaybeBenchmarkLog,
ElementsAre(Field(&BenchmarkState::LastSampleIterations, 1),
Field(&BenchmarkState::LastSampleIterations, 2),
Field(&BenchmarkState::LastSampleIterations, 4),
Field(&BenchmarkState::LastSampleIterations, 8)));
EXPECT_THAT(Result.MaybeBenchmarkLog->size(), 4);
EXPECT_THAT(Result.TerminationStatus, BenchmarkStatus::PrecisionReached);
const auto result = run();
EXPECT_THAT(*result.maybe_benchmark_log,
ElementsAre(Field(&BenchmarkState::last_sample_iterations, 1),
Field(&BenchmarkState::last_sample_iterations, 2),
Field(&BenchmarkState::last_sample_iterations, 4),
Field(&BenchmarkState::last_sample_iterations, 8)));
EXPECT_THAT(result.maybe_benchmark_log->size(), 4);
EXPECT_THAT(result.termination_status, BenchmarkStatus::PrecisionReached);
}
TEST(ArrayRefLoop, Cycle) {

View file

@ -10,32 +10,32 @@ namespace libc_benchmarks {
using MemcpyFunction = void *(*)(void *__restrict, const void *__restrict,
size_t);
struct MemcpyConfiguration {
MemcpyFunction Function;
llvm::StringRef Name;
MemcpyFunction function;
llvm::StringRef name;
};
using MemmoveFunction = void *(*)(void *, const void *, size_t);
struct MemmoveConfiguration {
MemmoveFunction Function;
llvm::StringRef Name;
MemmoveFunction function;
llvm::StringRef name;
};
using MemsetFunction = void *(*)(void *, int, size_t);
struct MemsetConfiguration {
MemsetFunction Function;
llvm::StringRef Name;
MemsetFunction function;
llvm::StringRef name;
};
using BzeroFunction = void (*)(void *, size_t);
struct BzeroConfiguration {
BzeroFunction Function;
llvm::StringRef Name;
BzeroFunction function;
llvm::StringRef name;
};
using MemcmpOrBcmpFunction = int (*)(const void *, const void *, size_t);
struct MemcmpOrBcmpConfiguration {
MemcmpOrBcmpFunction Function;
llvm::StringRef Name;
MemcmpOrBcmpFunction function;
llvm::StringRef name;
};
} // namespace libc_benchmarks

View file

@ -8,10 +8,18 @@
#include "LibcMemoryBenchmark.h"
#include "llvm/ADT/SmallVector.h"
#include <iostream>
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
#include "llvm/ADT/Twine.h"
#include "llvm/Support/ErrorHandling.h"
#endif
#include "llvm/Support/MathExtras.h"
#include <algorithm>
#include <optional>
#if !defined(LIBC_BENCHMARKS_HAS_LLVM_SUPPORT) && __has_include(<unistd.h>)
#include <unistd.h>
#endif
namespace llvm {
namespace libc_benchmarks {
@ -62,13 +70,19 @@ MismatchOffsetDistribution::MismatchOffsetDistribution(size_t BufferSize,
}
static size_t getL1DataCacheSize() {
const std::vector<CacheInfo> &CacheInfos = HostState::get().Caches;
const auto IsL1DataCache = [](const CacheInfo &CI) {
return CI.Type == "Data" && CI.Level == 1;
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
const std::vector<CacheInfo> &cache_infos = HostState::get().caches;
const auto is_l1_data_cache = [](const CacheInfo &ci) {
return ci.type == "Data" && ci.level == 1;
};
const auto CacheIt = find_if(CacheInfos, IsL1DataCache);
if (CacheIt != CacheInfos.end())
return CacheIt->Size;
const auto cache_it = find_if(cache_infos, is_l1_data_cache);
if (cache_it != cache_infos.end())
return cache_it->size;
#elif defined(_SC_LEVEL1_DCACHE_SIZE)
long res = sysconf(_SC_LEVEL1_DCACHE_SIZE);
if (res > 0)
return static_cast<size_t>(res);
#endif
report_fatal_error("Unable to read L1 Cache Data Size");
}
@ -81,55 +95,64 @@ static size_t getAvailableBufferSize() {
}
ParameterBatch::ParameterBatch(size_t BufferCount)
: BufferSize(getAvailableBufferSize() / BufferCount),
BatchSize(ParameterStorageBytes / sizeof(ParameterType)),
Parameters(BatchSize) {
if (BufferSize <= 0 || BatchSize < 100)
: buffer_size(getAvailableBufferSize() / BufferCount),
batch_size(ParameterStorageBytes / sizeof(ParameterType)),
parameters(batch_size) {
if (buffer_size <= 0 || batch_size < 100)
report_fatal_error("Not enough L1 cache");
const size_t ParameterBytes = Parameters.size() * sizeof(ParameterType);
const size_t BufferBytes = BufferSize * BufferCount;
if (ParameterBytes + BufferBytes + L1LeftAsideBytes > getL1DataCacheSize())
const size_t parameter_bytes = parameters.size() * sizeof(ParameterType);
const size_t buffer_bytes = buffer_size * BufferCount;
if (parameter_bytes + buffer_bytes + L1LeftAsideBytes > getL1DataCacheSize())
report_fatal_error(
"We're splitting a buffer of the size of the L1 cache between a data "
"buffer and a benchmark parameters buffer, so by construction the "
"total should not exceed the size of the L1 cache");
}
size_t ParameterBatch::getBatchBytes() const {
size_t BatchBytes = 0;
for (auto &P : Parameters)
BatchBytes += P.SizeBytes;
return BatchBytes;
size_t ParameterBatch::get_batch_bytes() const {
size_t batch_bytes = 0;
for (auto &p : parameters)
batch_bytes += p.size_bytes;
return batch_bytes;
}
void ParameterBatch::checkValid(const ParameterType &P) const {
if (P.OffsetBytes + P.SizeBytes >= BufferSize)
void ParameterBatch::check_valid(const ParameterType &p) const {
if (p.offset_bytes + p.size_bytes >= buffer_size) {
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
report_fatal_error(
llvm::Twine("Call would result in buffer overflow: Offset=")
.concat(llvm::Twine(P.OffsetBytes))
.concat(llvm::Twine(p.offset_bytes))
.concat(", Size=")
.concat(llvm::Twine(P.SizeBytes))
.concat(llvm::Twine(p.size_bytes))
.concat(", BufferSize=")
.concat(llvm::Twine(BufferSize)));
.concat(llvm::Twine(buffer_size)));
#else
std::string message = "Call would result in buffer overflow: Offset=" +
std::to_string(p.offset_bytes) +
", Size=" + std::to_string(p.size_bytes) +
", BufferSize=" + std::to_string(buffer_size);
report_fatal_error(message.c_str());
#endif
}
}
CopySetup::CopySetup()
: ParameterBatch(2), SrcBuffer(ParameterBatch::BufferSize),
DstBuffer(ParameterBatch::BufferSize) {}
: ParameterBatch(2), src_buffer(ParameterBatch::buffer_size),
dst_buffer(ParameterBatch::buffer_size) {}
MoveSetup::MoveSetup()
: ParameterBatch(3), Buffer(ParameterBatch::BufferSize * 3) {}
: ParameterBatch(3), buffer(ParameterBatch::buffer_size * 3) {}
ComparisonSetup::ComparisonSetup()
: ParameterBatch(2), LhsBuffer(ParameterBatch::BufferSize),
RhsBuffer(ParameterBatch::BufferSize) {
: ParameterBatch(2), lhs_buffer(ParameterBatch::buffer_size),
rhs_buffer(ParameterBatch::buffer_size) {
// The memcmp buffers always compare equal.
memset(LhsBuffer.begin(), 0xF, BufferSize);
memset(RhsBuffer.begin(), 0xF, BufferSize);
memset(lhs_buffer.begin(), 0xF, buffer_size);
memset(rhs_buffer.begin(), 0xF, buffer_size);
}
SetSetup::SetSetup()
: ParameterBatch(1), DstBuffer(ParameterBatch::BufferSize) {}
: ParameterBatch(1), dst_buffer(ParameterBatch::buffer_size) {}
} // namespace libc_benchmarks
} // namespace llvm

View file

@ -21,6 +21,7 @@
#include <cstdint>
#include <optional>
#include <random>
#include <vector>
namespace llvm {
namespace libc_benchmarks {
@ -33,57 +34,57 @@ struct StudyConfiguration {
// One of 'memcpy', 'memset', 'memcmp'.
// The underlying implementation is always the llvm libc one.
// e.g. 'memcpy' will test 'LIBC_NAMESPACE::memcpy'
std::string Function;
std::string function;
// The number of trials to run for this benchmark.
// If in SweepMode, each individual sizes are measured 'NumTrials' time.
// i.e 'NumTrials' measurements for 0, 'NumTrials' measurements for 1 ...
uint32_t NumTrials = 1;
uint32_t num_trials = 1;
// Toggles between Sweep Mode and Distribution Mode (default).
// See 'SweepModeMaxSize' and 'SizeDistributionName' below.
bool IsSweepMode = false;
bool is_sweep_mode = false;
// Maximum size to use when measuring a ramp of size values (SweepMode).
// The benchmark measures all sizes from 0 to SweepModeMaxSize.
// Note: in sweep mode the same size is sampled several times in a row this
// will allow the processor to learn it and optimize the branching pattern.
// The resulting measurement is likely to be idealized.
uint32_t SweepModeMaxSize = 0; // inclusive
uint32_t sweep_mode_max_size = 0; // inclusive
// The name of the distribution to be used to randomize the size parameter.
// This is used when SweepMode is false (default).
std::string SizeDistributionName;
std::string size_distribution_name;
// This parameter allows to control how the buffers are accessed during
// benchmark:
// None : Use a fixed address that is at least cache line aligned,
// 1 : Use random address,
// >1 : Use random address aligned to value.
MaybeAlign AccessAlignment = std::nullopt;
MaybeAlign access_alignment = std::nullopt;
// When Function == 'memcmp', this is the buffers mismatch position.
// 0 : Buffers always compare equal,
// >0 : Buffers compare different at byte N-1.
uint32_t MemcmpMismatchAt = 0;
uint32_t memcmp_mismatch_at = 0;
};
struct Runtime {
// Details about the Host (cpu name, cpu frequency, cache hierarchy).
HostState Host;
HostState host;
// The framework will populate this value so all data accessed during the
// benchmark will stay in L1 data cache. This includes bookkeeping data.
uint32_t BufferSize = 0;
uint32_t buffer_size = 0;
// This is the number of distinct parameters used in a single batch.
// The framework always tests a batch of randomized parameter to prevent the
// cpu from learning branching patterns.
uint32_t BatchParameterCount = 0;
uint32_t batch_parameter_count = 0;
// The benchmark options that were used to perform the measurement.
// This is decided by the framework.
BenchmarkOptions BenchmarkOptions;
llvm::libc_benchmarks::BenchmarkOptions benchmark_options;
};
//--------
@ -92,10 +93,10 @@ struct Runtime {
// The root object containing all the data (configuration and measurements).
struct Study {
std::string StudyName;
Runtime Runtime;
StudyConfiguration Configuration;
std::vector<Duration> Measurements;
std::string study_name;
llvm::libc_benchmarks::Runtime runtime;
StudyConfiguration configuration;
std::vector<Duration> measurements;
};
//------
@ -139,12 +140,20 @@ public:
}
};
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
using MismatchIndicesType = llvm::SmallVector<uint32_t, 16>;
using MismatchIndicesImplType = llvm::SmallVectorImpl<uint32_t>;
#else
using MismatchIndicesType = std::vector<uint32_t>;
using MismatchIndicesImplType = std::vector<uint32_t>;
#endif
// Helper to generate random buffer offsets that satisfy the configuration
// constraints. It is specifically designed to benchmark `memcmp` functions
// where we may want the Nth byte to differ.
class MismatchOffsetDistribution {
std::uniform_int_distribution<size_t> MismatchIndexSelector;
llvm::SmallVector<uint32_t, 16> MismatchIndices;
MismatchIndicesType MismatchIndices;
const uint32_t MismatchAt;
public:
@ -153,7 +162,7 @@ public:
explicit operator bool() const { return !MismatchIndices.empty(); }
const llvm::SmallVectorImpl<uint32_t> &getMismatchIndices() const {
const MismatchIndicesImplType &getMismatchIndices() const {
return MismatchIndices;
}
@ -172,21 +181,21 @@ public:
/// ParameterType can all fit in the L1 cache.
struct ParameterBatch {
struct ParameterType {
unsigned OffsetBytes : 16; // max : 16 KiB - 1
unsigned SizeBytes : 16; // max : 16 KiB - 1
unsigned offset_bytes : 16; // max : 16 KiB - 1
unsigned size_bytes : 16; // max : 16 KiB - 1
};
ParameterBatch(size_t BufferCount);
/// Verifies that memory accessed through this parameter is valid.
void checkValid(const ParameterType &) const;
void check_valid(const ParameterType &) const;
/// Computes the number of bytes processed during within this batch.
size_t getBatchBytes() const;
size_t get_batch_bytes() const;
const size_t BufferSize;
const size_t BatchSize;
std::vector<ParameterType> Parameters;
const size_t buffer_size;
const size_t batch_size;
std::vector<ParameterType> parameters;
};
/// Provides source and destination buffers for the Copy operation as well as
@ -194,18 +203,19 @@ struct ParameterBatch {
struct CopySetup : public ParameterBatch {
CopySetup();
inline static const ArrayRef<MemorySizeDistribution> getDistributions() {
inline static const ArrayRef<MemorySizeDistribution> get_distributions() {
return getMemcpySizeDistributions();
}
inline void *Call(ParameterType Parameter, MemcpyFunction Memcpy) {
return Memcpy(DstBuffer + Parameter.OffsetBytes,
SrcBuffer + Parameter.OffsetBytes, Parameter.SizeBytes);
inline void *call(ParameterType parameter, MemcpyFunction memcpy_func) {
return memcpy_func(dst_buffer + parameter.offset_bytes,
src_buffer + parameter.offset_bytes,
parameter.size_bytes);
}
private:
AlignedBuffer SrcBuffer;
AlignedBuffer DstBuffer;
AlignedBuffer src_buffer;
AlignedBuffer dst_buffer;
};
/// Provides source and destination buffers for the Move operation as well as
@ -213,17 +223,17 @@ private:
struct MoveSetup : public ParameterBatch {
MoveSetup();
inline static const ArrayRef<MemorySizeDistribution> getDistributions() {
inline static const ArrayRef<MemorySizeDistribution> get_distributions() {
return getMemmoveSizeDistributions();
}
inline void *Call(ParameterType Parameter, MemmoveFunction Memmove) {
return Memmove(Buffer + ParameterBatch::BufferSize / 3,
Buffer + Parameter.OffsetBytes, Parameter.SizeBytes);
inline void *call(ParameterType parameter, MemmoveFunction memmove_func) {
return memmove_func(buffer + ParameterBatch::buffer_size / 3,
buffer + parameter.offset_bytes, parameter.size_bytes);
}
private:
AlignedBuffer Buffer;
AlignedBuffer buffer;
};
/// Provides destination buffer for the Set operation as well as the associated
@ -231,22 +241,22 @@ private:
struct SetSetup : public ParameterBatch {
SetSetup();
inline static const ArrayRef<MemorySizeDistribution> getDistributions() {
inline static const ArrayRef<MemorySizeDistribution> get_distributions() {
return getMemsetSizeDistributions();
}
inline void *Call(ParameterType Parameter, MemsetFunction Memset) {
return Memset(DstBuffer + Parameter.OffsetBytes,
Parameter.OffsetBytes % 0xFF, Parameter.SizeBytes);
inline void *call(ParameterType parameter, MemsetFunction memset_func) {
return memset_func(dst_buffer + parameter.offset_bytes,
parameter.offset_bytes % 0xFF, parameter.size_bytes);
}
inline void *Call(ParameterType Parameter, BzeroFunction Bzero) {
Bzero(DstBuffer + Parameter.OffsetBytes, Parameter.SizeBytes);
return DstBuffer.begin();
inline void *call(ParameterType parameter, BzeroFunction bzero_func) {
bzero_func(dst_buffer + parameter.offset_bytes, parameter.size_bytes);
return dst_buffer.begin();
}
private:
AlignedBuffer DstBuffer;
AlignedBuffer dst_buffer;
};
/// Provides left and right buffers for the Comparison operation as well as the
@ -254,18 +264,20 @@ private:
struct ComparisonSetup : public ParameterBatch {
ComparisonSetup();
inline static const ArrayRef<MemorySizeDistribution> getDistributions() {
inline static const ArrayRef<MemorySizeDistribution> get_distributions() {
return getMemcmpSizeDistributions();
}
inline int Call(ParameterType Parameter, MemcmpOrBcmpFunction MemcmpOrBcmp) {
return MemcmpOrBcmp(LhsBuffer + Parameter.OffsetBytes,
RhsBuffer + Parameter.OffsetBytes, Parameter.SizeBytes);
inline int call(ParameterType parameter,
MemcmpOrBcmpFunction memcmp_or_bcmp_func) {
return memcmp_or_bcmp_func(lhs_buffer + parameter.offset_bytes,
rhs_buffer + parameter.offset_bytes,
parameter.size_bytes);
}
private:
AlignedBuffer LhsBuffer;
AlignedBuffer RhsBuffer;
AlignedBuffer lhs_buffer;
AlignedBuffer rhs_buffer;
};
} // namespace libc_benchmarks

View file

@ -102,47 +102,47 @@ struct MemfunctionBenchmarkBase : public BenchmarkSetup {
virtual Study run() = 0;
CircularArrayRef<ParameterBatch::ParameterType>
generateBatch(size_t Iterations) {
generate_batch(size_t iterations) {
randomize();
return cycle(ArrayRef(Parameters), Iterations);
return cycle(ArrayRef(parameters), iterations);
}
protected:
Study createStudy() {
Study Study;
Study study;
// Setup study.
Study.StudyName = StudyName;
Runtime &RI = Study.Runtime;
RI.Host = HostState::get();
RI.BufferSize = BufferSize;
RI.BatchParameterCount = BatchSize;
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.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;
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.NumTrials = NumTrials;
SC.IsSweepMode = SweepMode;
SC.AccessAlignment = MaybeAlign(AlignedAccess);
SC.Function = LIBC_BENCHMARK_FUNCTION_NAME;
return Study;
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) {
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);
const BenchmarkResult result = benchmark(
options, *this, [this](ParameterBatch::ParameterType parameter) {
return call(parameter, LIBC_BENCHMARK_FUNCTION);
});
Measurements.push_back(Result.BestGuess);
reportProgress(Measurements);
measurements.push_back(result.best_guess);
reportProgress(measurements);
}
}
@ -173,72 +173,73 @@ private:
struct MemfunctionBenchmarkSweep final : public MemfunctionBenchmarkBase {
MemfunctionBenchmarkSweep()
: OffsetSampler(MemfunctionBenchmarkBase::BufferSize, SweepMaxSize,
MaybeAlign(AlignedAccess)) {}
: offset_sampler(MemfunctionBenchmarkBase::buffer_size, SweepMaxSize,
MaybeAlign(AlignedAccess)) {}
virtual void randomize() override {
for (auto &P : Parameters) {
P.OffsetBytes = OffsetSampler(Gen);
P.SizeBytes = CurrentSweepSize;
checkValid(P);
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.SweepModeMaxSize = SweepMaxSize;
BenchmarkOptions &BO = Study.Runtime.BenchmarkOptions;
BO.MinDuration = std::chrono::milliseconds(1);
BO.InitialIterations = 100;
auto &Measurements = Study.Measurements;
Measurements.reserve(NumTrials * SweepMaxSize);
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) {
CurrentSweepSize = Size;
runTrials(BO, Measurements);
current_sweep_size = Size;
runTrials(bo, measurements);
}
return Study;
return study;
}
private:
size_t CurrentSweepSize = 0;
OffsetDistribution OffsetSampler;
std::mt19937_64 Gen;
size_t current_sweep_size = 0;
OffsetDistribution offset_sampler;
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)) {}
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.OffsetBytes = OffsetSampler(Gen);
P.SizeBytes = SizeSampler(Gen);
checkValid(P);
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.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;
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> SizeSampler;
OffsetDistribution OffsetSampler;
std::mt19937_64 Gen;
MemorySizeDistribution distribution;
ArrayRef<double> probabilities;
std::discrete_distribution<unsigned> size_sampler;
OffsetDistribution offset_sampler;
std::mt19937_64 gen;
};
void writeStudy(const Study &S) {
@ -270,7 +271,7 @@ void main() {
Benchmark.reset(new MemfunctionBenchmarkSweep());
else
Benchmark.reset(new MemfunctionBenchmarkDistribution(getDistributionOrDie(
BenchmarkSetup::getDistributions(), SizeDistributionName)));
BenchmarkSetup::get_distributions(), SizeDistributionName)));
writeStudy(Benchmark->run());
}

View file

@ -3,7 +3,9 @@
#include "MemorySizeDistributions.h"
#include "benchmark/benchmark.h"
#include "llvm/ADT/ArrayRef.h"
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
#include "llvm/ADT/Twine.h"
#endif
#include <chrono>
#include <cstdint>
#include <random>
@ -11,7 +13,9 @@
using llvm::Align;
using llvm::ArrayRef;
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
using llvm::Twine;
#endif
using llvm::libc_benchmarks::BzeroConfiguration;
using llvm::libc_benchmarks::ComparisonSetup;
using llvm::libc_benchmarks::CopySetup;
@ -35,57 +39,61 @@ static std::mt19937_64 &getGenerator() {
template <typename SetupType, typename ConfigurationType> struct Runner {
Runner(benchmark::State &S, llvm::ArrayRef<ConfigurationType> Configurations)
: State(S), Distribution(SetupType::getDistributions()[State.range(0)]),
Probabilities(Distribution.Probabilities),
SizeSampler(Probabilities.begin(), Probabilities.end()),
OffsetSampler(Setup.BufferSize, Probabilities.size() - 1,
kBenchmarkAlignment),
Configuration(Configurations[State.range(1)]) {
for (auto &P : Setup.Parameters) {
P.OffsetBytes = OffsetSampler(getGenerator());
P.SizeBytes = SizeSampler(getGenerator());
Setup.checkValid(P);
: State(S), distribution(SetupType::get_distributions()[State.range(0)]),
probabilities(distribution.probabilities),
size_sampler(probabilities.begin(), probabilities.end()),
offset_sampler(Setup.buffer_size, probabilities.size() - 1,
kBenchmarkAlignment),
configuration(Configurations[State.range(1)]) {
for (auto &p : Setup.parameters) {
p.offset_bytes = offset_sampler(getGenerator());
p.size_bytes = size_sampler(getGenerator());
Setup.check_valid(p);
}
}
~Runner() {
const size_t TotalBytes =
(State.iterations() * Setup.getBatchBytes()) / Setup.BatchSize;
State.SetBytesProcessed(TotalBytes);
const size_t total_bytes =
(State.iterations() * Setup.get_batch_bytes()) / Setup.batch_size;
State.SetBytesProcessed(total_bytes);
State.SetItemsProcessed(State.iterations());
State.SetLabel((Twine(Configuration.Name) + "," + Distribution.Name).str());
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
State.SetLabel((Twine(configuration.name) + "," + distribution.name).str());
#else
State.SetLabel(configuration.name.str() + "," + distribution.name.str());
#endif
State.counters["bytes_per_cycle"] = benchmark::Counter(
TotalBytes / benchmark::CPUInfo::Get().cycles_per_second,
total_bytes / benchmark::CPUInfo::Get().cycles_per_second,
benchmark::Counter::kIsRate);
}
inline void runBatch() {
for (const auto &P : Setup.Parameters)
benchmark::DoNotOptimize(Setup.Call(P, Configuration.Function));
inline void run_batch() {
for (const auto &p : Setup.parameters)
benchmark::DoNotOptimize(Setup.call(p, configuration.function));
}
size_t getBatchSize() const { return Setup.BatchSize; }
size_t get_batch_size() const { return Setup.batch_size; }
private:
SetupType Setup;
benchmark::State &State;
MemorySizeDistribution Distribution;
ArrayRef<double> Probabilities;
std::discrete_distribution<unsigned> SizeSampler;
OffsetDistribution OffsetSampler;
ConfigurationType Configuration;
MemorySizeDistribution distribution;
ArrayRef<double> probabilities;
std::discrete_distribution<unsigned> size_sampler;
OffsetDistribution offset_sampler;
ConfigurationType configuration;
};
#define BENCHMARK_MEMORY_FUNCTION(BM_NAME, SETUP, CONFIGURATION_TYPE, \
CONFIGURATION_ARRAY_REF) \
void BM_NAME(benchmark::State &State) { \
Runner<SETUP, CONFIGURATION_TYPE> Setup(State, CONFIGURATION_ARRAY_REF); \
const size_t BatchSize = Setup.getBatchSize(); \
while (State.KeepRunningBatch(BatchSize)) \
Setup.runBatch(); \
const size_t batch_size_val = Setup.get_batch_size(); \
while (State.KeepRunningBatch(batch_size_val)) \
Setup.run_batch(); \
} \
BENCHMARK(BM_NAME)->Apply([](benchmark::internal::Benchmark *benchmark) { \
const int64_t DistributionSize = SETUP::getDistributions().size(); \
const int64_t DistributionSize = SETUP::get_distributions().size(); \
const int64_t ConfigurationSize = CONFIGURATION_ARRAY_REF.size(); \
for (int64_t DistIndex = 0; DistIndex < DistributionSize; ++DistIndex) \
for (int64_t ConfIndex = 0; ConfIndex < ConfigurationSize; ++ConfIndex) \

View file

@ -1,7 +1,12 @@
#include "MemorySizeDistributions.h"
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#else
#include <iostream>
#include <sstream>
#endif
namespace llvm {
namespace libc_benchmarks {
@ -186,16 +191,25 @@ MemorySizeDistribution
getDistributionOrDie(ArrayRef<MemorySizeDistribution> Distributions,
StringRef Name) {
for (const auto &MSD : Distributions)
if (MSD.Name == Name)
if (MSD.name == Name)
return MSD;
#ifdef LIBC_BENCHMARKS_HAS_LLVM_SUPPORT
std::string Message;
raw_string_ostream Stream(Message);
Stream << "Unknown MemorySizeDistribution '" << Name
<< "', available distributions:\n";
for (const auto &MSD : Distributions)
Stream << "'" << MSD.Name << "'\n";
Stream << "'" << MSD.name << "'\n";
report_fatal_error(Message);
#else
std::stringstream Stream;
Stream << "Unknown MemorySizeDistribution '" << std::string(Name)
<< "', available distributions:\n";
for (const auto &MSD : Distributions)
Stream << "'" << MSD.name.str() << "'\n";
report_fatal_error(Stream.str());
#endif
}
} // namespace libc_benchmarks

View file

@ -25,8 +25,8 @@ namespace libc_benchmarks {
/// A simple POD exposing caracteristics of a memory function size
/// distributions. The underlying data is immutable.
struct MemorySizeDistribution {
StringRef Name; // The name of the distribution.
ArrayRef<double> Probabilities; // Size indexed array of probabilities.
StringRef name; // The name of the distribution.
ArrayRef<double> probabilities; // Size indexed array of probabilities.
};
/// Returns a list of memmove size distributions.