[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:
parent
aa939e521d
commit
aca3f14c0d
13 changed files with 674 additions and 518 deletions
|
|
@ -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()
|
||||
|
||||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue