Split benchmark into more specialized, nontemplated structs.

This commit is contained in:
Allison Vacanti
2020-12-29 18:11:42 -05:00
parent 093077de5f
commit beaead2c3f
9 changed files with 438 additions and 106 deletions

View File

@@ -1,5 +1,7 @@
set(srcs
axes_metadata.cu
axis_base.cu
benchmark_base.cu
float64_axis.cu
int64_axis.cu
params.cu

93
nvbench/axes_metadata.cu Normal file
View File

@@ -0,0 +1,93 @@
#include <nvbench/axes_metadata.cuh>
#include <fmt/format.h>
#include <algorithm>
#include <stdexcept>
namespace nvbench
{
void axes_metadata::add_float64_axis(std::string name,
std::vector<nvbench::float64_t> data)
{
auto axis = std::make_unique<nvbench::float64_axis>(std::move(name));
axis->set_inputs(std::move(data));
m_axes.push_back(std::move(axis));
}
void axes_metadata::add_int64_axis(std::string name,
std::vector<nvbench::int64_t> data,
nvbench::int64_axis_flags flags)
{
auto axis = std::make_unique<nvbench::int64_axis>(std::move(name), flags);
axis->set_inputs(std::move(data));
m_axes.push_back(std::move(axis));
}
void axes_metadata::add_string_axis(std::string name,
std::vector<std::string> data)
{
auto axis = std::make_unique<nvbench::string_axis>(std::move(name));
axis->set_inputs(std::move(data));
m_axes.push_back(std::move(axis));
}
const int64_axis &axes_metadata::get_int64_axis(std::string_view name) const
{
const auto &axis = this->get_axis(name, nvbench::axis_type::int64);
return static_cast<const nvbench::int64_axis&>(axis);
}
const float64_axis &axes_metadata::get_float64_axis(std::string_view name) const
{
const auto &axis = this->get_axis(name, nvbench::axis_type::float64);
return static_cast<const nvbench::float64_axis&>(axis);
}
const string_axis &axes_metadata::get_string_axis(std::string_view name) const
{
const auto &axis = this->get_axis(name, nvbench::axis_type::string);
return static_cast<const nvbench::string_axis&>(axis);
}
const type_axis &axes_metadata::get_type_axis(std::string_view name) const
{
const auto &axis = this->get_axis(name, nvbench::axis_type::type);
return static_cast<const nvbench::type_axis&>(axis);
}
const axis_base &axes_metadata::get_axis(std::string_view name) const
{
auto iter =
std::find_if(m_axes.cbegin(), m_axes.cend(), [&name](const auto &axis) {
return axis->get_name() == name;
});
if (iter == m_axes.cend())
{
throw std::runtime_error(
fmt::format("{}:{}: Axis '{}' not found.", __FILE__, __LINE__, name));
}
return **iter;
}
const axis_base &axes_metadata::get_axis(std::string_view name,
nvbench::axis_type type) const
{
const auto &axis = this->get_axis(name);
if (axis.get_type() != type)
{
throw std::runtime_error(fmt::format("{}:{}: Axis '{}' type mismatch "
"(expected {}, actual {}).",
__FILE__,
__LINE__,
name,
type,
axis.get_type()));
}
return axis;
}
} // namespace nvbench

80
nvbench/axes_metadata.cuh Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include <nvbench/float64_axis.cuh>
#include <nvbench/int64_axis.cuh>
#include <nvbench/string_axis.cuh>
#include <nvbench/type_axis.cuh>
#include <nvbench/types.cuh>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace nvbench
{
// Holds dynamic axes information.
struct axes_metadata
{
using axes_type = std::vector<std::unique_ptr<nvbench::axis_base>>;
template <typename type_axes>
void set_type_axes_names(std::vector<std::string> names);
void add_int64_axis(std::string name,
std::vector<nvbench::int64_t> data,
nvbench::int64_axis_flags flags);
void add_float64_axis(std::string name, std::vector<nvbench::float64_t> data);
void add_string_axis(std::string name, std::vector<std::string> data);
[[nodiscard]] const nvbench::int64_axis &
get_int64_axis(std::string_view name) const;
[[nodiscard]] const nvbench::float64_axis &
get_float64_axis(std::string_view name) const;
[[nodiscard]] const nvbench::string_axis &
get_string_axis(std::string_view name) const;
[[nodiscard]] const nvbench::type_axis &
get_type_axis(std::string_view name) const;
[[nodiscard]] const axes_type &get_axes() const { return m_axes; }
[[nodiscard]] const nvbench::axis_base &get_axis(std::string_view name) const;
[[nodiscard]] const nvbench::axis_base &
get_axis(std::string_view name, nvbench::axis_type type) const;
private:
axes_type m_axes;
};
template <typename type_axes>
void axes_metadata::set_type_axes_names(std::vector<std::string> names)
{
if (names.size() != nvbench::tl::size<type_axes>::value)
{ // TODO Find a way to get a better error message w/o bringing fmt
// into this header.
throw std::runtime_error("set_type_axes_names(): len(names) != "
"len(type_axes)");
}
auto names_iter = names.begin(); // contents will be moved from
nvbench::tl::foreach<type_axes>([&axes = m_axes, &names_iter](
[[maybe_unused]] auto wrapped_type) {
// Note:
// The word "type" appears 6 times in the next line.
// Every. Single. Token.
// Take a moment to just appreciate this beautiful language:
typedef typename decltype(wrapped_type)::type type_list;
auto axis = std::make_unique<nvbench::type_axis>(std::move(*names_iter++));
axis->set_inputs<type_list>();
axes.push_back(std::move(axis));
});
}
} // namespace nvbench

View File

@@ -1,111 +1,35 @@
#pragma once
#include <nvbench/float64_axis.cuh>
#include <nvbench/int64_axis.cuh>
#include <nvbench/string_axis.cuh>
#include <nvbench/type_axis.cuh>
#include <nvbench/benchmark_base.cuh>
#include <memory>
#include <stdexcept>
#include <vector>
#include <nvbench/axes_metadata.cuh>
namespace nvbench
{
template <typename Kernel, typename TypeAxes>
struct benchmark
// See benchmark_base for actual user API.
template <typename KernelGenerator, typename TypeAxes>
struct benchmark final : public benchmark_base
{
using kernel_type = Kernel;
using type_axes = TypeAxes;
using type_configs = nvbench::tl::cartesian_product<type_axes>;
using kernel_generator = KernelGenerator;
using type_axes = TypeAxes;
using type_configs = nvbench::tl::cartesian_product<type_axes>;
static constexpr std::size_t num_type_configs =
nvbench::tl::size<type_configs>{};
void set_name(std::string name) { m_name = std::move(name); }
const std::string &get_name() const { return m_name; }
using benchmark_base::benchmark_base;
// Convenience API for a single type_axis.
benchmark &set_type_axes_name(std::string name)
{
return this->set_type_axes_names({std::move(name)});
}
benchmark &set_type_axes_names(std::vector<std::string> names);
benchmark &add_float64_axis(std::string name,
std::vector<nvbench::float64_t> data)
{
auto axis = std::make_unique<nvbench::float64_axis>(std::move(name));
axis->set_inputs(std::move(data));
m_float64_axes.push_back(std::move(axis));
return *this;
}
benchmark &add_int64_axis(
std::string name,
std::vector<nvbench::int64_t> data,
nvbench::int64_axis_flags flags = nvbench::int64_axis_flags::none)
{
auto axis = std::make_unique<nvbench::int64_axis>(std::move(name), flags);
axis->set_inputs(std::move(data));
m_int64_axes.push_back(std::move(axis));
return *this;
}
benchmark &add_int64_power_of_two_axis(std::string name,
std::vector<nvbench::int64_t> data)
{
return this->add_int64_axis(std::move(name),
std::move(data),
nvbench::int64_axis_flags::power_of_two);
}
benchmark &add_string_axis(std::string name, std::vector<std::string> data)
{
auto axis = std::make_unique<nvbench::string_axis>(std::move(name));
axis->set_inputs(std::move(data));
m_string_axes.push_back(std::move(axis));
return *this;
}
[[nodiscard]] const auto &get_float64_axes() const { return m_float64_axes; }
[[nodiscard]] const auto &get_int64_axes() const { return m_int64_axes; }
[[nodiscard]] const auto &get_string_axes() const { return m_string_axes; }
[[nodiscard]] const auto &get_type_axes() const { return m_type_axes; }
// Note that this inline virtual dtor may cause vtable issues if linking
// multiple benchmarks with the same name / definition together. That's not a
// likely scenario, so we'll deal with that if it comes up.
~benchmark() override = default;
private:
std::string m_name;
std::vector<std::unique_ptr<nvbench::float64_axis>> m_float64_axes;
std::vector<std::unique_ptr<nvbench::int64_axis>> m_int64_axes;
std::vector<std::unique_ptr<nvbench::string_axis>> m_string_axes;
std::vector<std::unique_ptr<nvbench::type_axis>> m_type_axes;
void do_set_type_axes_names(std::vector<std::string> names) override
{
m_axes.template set_type_axes_names<type_axes>(std::move(names));
}
};
template <typename Kernel, typename TypeAxes>
benchmark<Kernel, TypeAxes> &
benchmark<Kernel, TypeAxes>::set_type_axes_names(std::vector<std::string> names)
{
if (names.size() != nvbench::tl::size<type_axes>::value)
{ // TODO Find a way to get a better error message w/o bringing fmt
// into this header.
throw std::runtime_error("set_type_axes_names(): len(names) != "
"len(type_axes)");
}
auto names_iter = names.begin(); // contents will be moved from
nvbench::tl::foreach<type_axes>([&axes = m_type_axes, &names_iter](
[[maybe_unused]] auto wrapped_type) {
// Note:
// The word "type" appears 6 times in the next line.
// Every. Single. Token.
// Take a moment to just appreciate this beautiful language:
typedef typename decltype(wrapped_type)::type type_list;
auto axis = std::make_unique<nvbench::type_axis>(std::move(*names_iter++));
axis->set_inputs<type_list>();
axes.push_back(std::move(axis));
});
return *this;
}
} // namespace nvbench

View File

@@ -0,0 +1,8 @@
#include <nvbench/benchmark_base.cuh>
namespace nvbench
{
benchmark_base::~benchmark_base() = default;
} // namespace nvbench

View File

@@ -0,0 +1,79 @@
#pragma once
#include <nvbench/axes_metadata.cuh>
namespace nvbench
{
// Non-templated benchmark info. This is the class users will actually interact
// with when they use a benchmark creation macro.
struct benchmark_base
{
virtual ~benchmark_base();
benchmark_base &set_name(std::string name)
{
m_name = std::move(name);
return *this;
}
[[nodiscard]] const std::string &get_name() const { return m_name; }
// Convenience API for a single type_axis.
benchmark_base &set_type_axes_name(std::string name)
{
return this->set_type_axes_names({std::move(name)});
}
benchmark_base &set_type_axes_names(std::vector<std::string> names)
{
this->do_set_type_axes_names(std::move(names));
return *this;
}
benchmark_base &add_float64_axis(std::string name,
std::vector<nvbench::float64_t> data)
{
m_axes.add_float64_axis(std::move(name), std::move(data));
return *this;
}
benchmark_base &add_int64_axis(
std::string name,
std::vector<nvbench::int64_t> data,
nvbench::int64_axis_flags flags = nvbench::int64_axis_flags::none)
{
m_axes.add_int64_axis(std::move(name), std::move(data), flags);
return *this;
}
benchmark_base &add_int64_power_of_two_axis(std::string name,
std::vector<nvbench::int64_t> data)
{
return this->add_int64_axis(std::move(name),
std::move(data),
nvbench::int64_axis_flags::power_of_two);
}
benchmark_base &add_string_axis(std::string name,
std::vector<std::string> data)
{
m_axes.add_string_axis(std::move(name), std::move(data));
return *this;
}
[[nodiscard]] const nvbench::axes_metadata &get_axes() const
{
return m_axes;
}
protected:
std::string m_name;
nvbench::axes_metadata m_axes;
private:
// route this through a virtual so the templated subclass can inject type info
virtual void do_set_type_axes_names(std::vector<std::string> names) = 0;
};
} // namespace nvbench

View File

@@ -1,4 +1,5 @@
set(test_srcs
axes_metadata.cu
benchmark.cu
int64_axis.cu
float64_axis.cu

146
testing/axes_metadata.cu Normal file
View File

@@ -0,0 +1,146 @@
#include <nvbench/axes_metadata.cuh>
#include <nvbench/type_list.cuh>
#include <nvbench/type_strings.cuh>
#include <nvbench/types.cuh>
#include "test_asserts.cuh"
#include <fmt/format.h>
#include <algorithm>
#include <string_view>
using int_list = nvbench::type_list<nvbench::int8_t,
nvbench::int16_t,
nvbench::int32_t,
nvbench::int64_t>;
using float_list = nvbench::type_list<nvbench::float32_t, nvbench::float64_t>;
using misc_list = nvbench::type_list<bool, void>;
using three_type_axes = nvbench::type_list<int_list, float_list, misc_list>;
using no_types = nvbench::type_list<>;
void test_type_axes()
{
nvbench::axes_metadata axes;
axes.set_type_axes_names<three_type_axes>({"Integer", "Float", "Other"});
fmt::memory_buffer buffer;
for (const auto &axis : axes.get_type_axes())
{
fmt::format_to(buffer, "Axis: {}\n", axis->get_name());
const auto num_values = axis->get_size();
for (std::size_t i = 0; i < num_values; ++i)
{
auto input_string = axis->get_input_string(i);
auto description = axis->get_description(i);
fmt::format_to(buffer,
" - {}{}\n",
input_string,
description.empty() ? ""
: fmt::format(" ({})", description));
}
}
const std::string ref = R"expected(Axis: Integer
- I8 (int8_t)
- I16 (int16_t)
- I32 (int32_t)
- I64 (int64_t)
Axis: Float
- F32 (float)
- F64 (double)
Axis: Other
- bool
- void
)expected";
const std::string test = fmt::to_string(buffer);
const auto diff =
std::mismatch(ref.cbegin(), ref.cend(), test.cbegin(), test.cend());
const auto idx = diff.second - test.cbegin();
ASSERT_MSG(test == ref,
fmt::format("Differs at character {}.\n"
"Expected:\n\"{}\"\n\n"
"Actual:\n\"{}\"\n-- ERROR --\n\"{}\"",
idx,
ref,
std::string_view(test.c_str(), idx),
std::string_view(test.c_str() + idx,
test.size() - idx)));
}
void test_float64_axes()
{
nvbench::axes_metadata axes;
axes.add_float64_axis("F64 Axis", {0., .1, .25, .5, 1.});
ASSERT(axes.get_float64_axes().size() == 1);
ASSERT(axes.get_float64_axes()[0] != nullptr);
const auto &axis = *axes.get_float64_axes()[0];
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 0.);
ASSERT(axis.get_value(1) == .1);
ASSERT(axis.get_value(2) == .25);
ASSERT(axis.get_value(3) == .5);
ASSERT(axis.get_value(4) == 1.);
}
void test_int64_axes()
{
nvbench::axes_metadata axes;
axes.add_int64_axis("I64 Axis",
{10, 11, 12, 13, 14},
nvbench::int64_axis_flags::none);
ASSERT(axes.get_int64_axes().size() == 1);
ASSERT(axes.get_int64_axes()[0] != nullptr);
const auto &axis = *axes.get_int64_axes()[0];
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 10);
ASSERT(axis.get_value(1) == 11);
ASSERT(axis.get_value(2) == 12);
ASSERT(axis.get_value(3) == 13);
ASSERT(axis.get_value(4) == 14);
}
void test_int64_power_of_two_axes()
{
nvbench::axes_metadata axes;
axes.add_int64_axis("I64 POT Axis",
{1, 2, 3, 4, 5},
nvbench::int64_axis_flags::power_of_two);
ASSERT(axes.get_int64_axes().size() == 1);
ASSERT(axes.get_int64_axes()[0] != nullptr);
const auto &axis = *axes.get_int64_axes()[0];
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 2);
ASSERT(axis.get_value(1) == 4);
ASSERT(axis.get_value(2) == 8);
ASSERT(axis.get_value(3) == 16);
ASSERT(axis.get_value(4) == 32);
}
void test_string_axes()
{
nvbench::axes_metadata axes;
axes.add_string_axis("Strings", {"string a", "string b", "string c"});
ASSERT(axes.get_string_axes().size() == 1);
ASSERT(axes.get_string_axes()[0] != nullptr);
const auto &axis = *axes.get_string_axes()[0];
ASSERT(axis.get_size() == 3);
ASSERT(axis.get_value(0) == "string a");
ASSERT(axis.get_value(1) == "string b");
ASSERT(axis.get_value(2) == "string c");
}
int main()
{
test_type_axes();
test_float64_axes();
test_int64_axes();
test_int64_power_of_two_axes();
test_string_axes();
}

View File

@@ -33,7 +33,7 @@ void test_type_axes()
bench.set_type_axes_names({"Integer", "Float", "Other"});
fmt::memory_buffer buffer;
const auto &axes = bench.get_type_axes();
const auto &axes = bench.get_axes().get_axes();
for (const auto &axis : axes)
{
fmt::format_to(buffer, "Axis: {}\n", axis->get_name());
@@ -119,9 +119,8 @@ void test_float64_axes()
{
no_types_bench bench;
bench.add_float64_axis("F64 Axis", {0., .1, .25, .5, 1.});
ASSERT(bench.get_float64_axes().size() == 1);
ASSERT(bench.get_float64_axes()[0] != nullptr);
const auto &axis = *bench.get_float64_axes()[0];
ASSERT(bench.get_axes().get_axes().size() == 1);
const auto &axis = bench.get_axes().get_float64_axis("F64 Axis");
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 0.);
ASSERT(axis.get_value(1) == .1);
@@ -134,9 +133,9 @@ void test_int64_axes()
{
no_types_bench bench;
bench.add_int64_axis("I64 Axis", {10, 11, 12, 13, 14});
ASSERT(bench.get_int64_axes().size() == 1);
ASSERT(bench.get_int64_axes()[0] != nullptr);
const auto &axis = *bench.get_int64_axes()[0];
ASSERT(bench.get_axes().get_axes().size() == 1);
ASSERT(bench.get_axes().get_axes()[0] != nullptr);
const auto &axis = bench.get_axes().get_int64_axis("I64 Axis");
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 10);
ASSERT(axis.get_value(1) == 11);
@@ -149,9 +148,9 @@ void test_int64_power_of_two_axes()
{
no_types_bench bench;
bench.add_int64_power_of_two_axis("I64 POT Axis", {1, 2, 3, 4, 5});
ASSERT(bench.get_int64_axes().size() == 1);
ASSERT(bench.get_int64_axes()[0] != nullptr);
const auto &axis = *bench.get_int64_axes()[0];
ASSERT(bench.get_axes().get_axes().size() == 1);
ASSERT(bench.get_axes().get_axes()[0] != nullptr);
const auto &axis = bench.get_axes().get_int64_axis("I64 POT Axis");
ASSERT(axis.get_size() == 5);
ASSERT(axis.get_value(0) == 2);
ASSERT(axis.get_value(1) == 4);
@@ -164,9 +163,9 @@ void test_string_axes()
{
no_types_bench bench;
bench.add_string_axis("Strings", {"string a", "string b", "string c"});
ASSERT(bench.get_string_axes().size() == 1);
ASSERT(bench.get_string_axes()[0] != nullptr);
const auto &axis = *bench.get_string_axes()[0];
ASSERT(bench.get_axes().get_axes().size() == 1);
ASSERT(bench.get_axes().get_axes()[0] != nullptr);
const auto &axis = bench.get_axes().get_string_axis("Strings");
ASSERT(axis.get_size() == 3);
ASSERT(axis.get_value(0) == "string a");
ASSERT(axis.get_value(1) == "string b");