make axis::variable allocator aware

This commit is contained in:
Hans Dembinski 2018-08-21 18:45:52 +02:00
parent 8216af927e
commit f1c59deb04
9 changed files with 212 additions and 107 deletions

View File

@ -11,7 +11,7 @@ int main() {
v.push_back(bh::axis::regular<>(100, -1, 1));
v.push_back(bh::axis::integer<>(1, 7));
// create dynamic histogram (make_static_histogram be used with iterators)
// create dynamic histogram (make_static_histogram cannot be used with iterators)
auto h = bh::make_dynamic_histogram(v.begin(), v.end());
// do something with h

View File

@ -21,6 +21,7 @@
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <algorithm>
// forward declaration for serialization
namespace boost {
@ -118,6 +119,7 @@ public:
using allocator_type = typename base_type::allocator_type;
using value_type = RealType;
using bin_type = interval_view<regular>;
using transform_type = Transform;
/** Construct axis with n bins over real range [lower, upper).
*
@ -129,10 +131,10 @@ public:
* \param trans arguments passed to the transform.
*/
regular(unsigned n, value_type lower, value_type upper, string_view label = {},
uoflow_type uo = uoflow_type::on, Transform trans = Transform(),
uoflow_type uo = uoflow_type::on, transform_type trans = transform_type(),
const allocator_type& a = allocator_type())
: base_type(n, uo, label, a)
, Transform(trans)
, transform_type(trans)
, min_(trans.forward(lower))
, delta_((trans.forward(upper) - trans.forward(lower)) / n) {
if (lower < upper) {
@ -152,7 +154,7 @@ public:
/// Returns the bin index for the passed argument.
int index(value_type x) const noexcept {
// Optimized code, measure impact of changes
const value_type z = (Transform::forward(x) - min_) / delta_;
const value_type z = (transform_type::forward(x) - min_) / delta_;
return z < base_type::size() ? (z >= 0.0 ? static_cast<int>(z) : -1)
: base_type::size();
}
@ -169,19 +171,19 @@ public:
const auto z = value_type(i) / n;
x = (1.0 - z) * min_ + z * (min_ + delta_ * n);
}
return Transform::inverse(x);
return transform_type::inverse(x);
}
bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); }
bool operator==(const regular& o) const noexcept {
return base_type::operator==(o) && Transform::operator==(o) && min_ == o.min_ &&
return base_type::operator==(o) && transform_type::operator==(o) && min_ == o.min_ &&
delta_ == o.delta_;
}
/// Access properties of the transform.
const Transform& transform() const noexcept {
return static_cast<const Transform&>(*this);
const transform_type& transform() const noexcept {
return static_cast<const transform_type&>(*this);
}
private:
@ -273,6 +275,10 @@ public:
using value_type = RealType;
using bin_type = interval_view<variable>;
private:
using value_allocator_type = typename std::allocator_traits<allocator_type>::template rebind_alloc<value_type>;
public:
/** Construct an axis from bin edges.
*
* \param x sequence of bin edges.
@ -281,42 +287,90 @@ public:
*/
variable(std::initializer_list<value_type> x, string_view label = {},
uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type())
: base_type(x.size() - 1, uo, label, a), x_(new value_type[x.size()]) {
if (x.size() >= 2) {
std::copy(x.begin(), x.end(), x_.get());
std::sort(x_.get(), x_.get() + base_type::size() + 1);
} else {
throw std::invalid_argument("at least two values required");
}
}
: variable(x.begin(), x.end(), label, uo, a)
{}
template <typename Iterator>
variable(Iterator begin, Iterator end, string_view label = {},
uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type())
: base_type(std::distance(begin, end) - 1, uo, label, a)
, x_(new value_type[std::distance(begin, end)]) {
std::copy(begin, end, x_.get());
std::sort(x_.get(), x_.get() + base_type::size() + 1);
: base_type(std::max(std::distance(begin, end), 1l) - 1, uo, label, a) {
value_allocator_type a2(a);
using AT = std::allocator_traits<value_allocator_type>;
x_ = AT::allocate(a2, base_type::size() + 1);
auto xit = x_;
AT::construct(a2, xit, *begin);
++begin;
while (begin != end) {
if (*begin <= *xit)
throw std::invalid_argument("input sequence must be strictly ascending");
AT::construct(a2, ++xit, *begin++);
}
}
variable() = default;
variable(const variable& o) : base_type(o), x_(new value_type[base_type::size() + 1]) {
std::copy(o.x_.get(), o.x_.get() + base_type::size() + 1, x_.get());
variable(const variable& o) : base_type(o) {
value_allocator_type a(o.get_allocator());
using AT = std::allocator_traits<value_allocator_type>;
x_ = AT::allocate(a, base_type::size() + 1);
auto it = o.x_;
const auto end = o.x_ + base_type::size() + 1;
auto xit = x_;
for (; it != end; ++it, ++xit)
AT::construct(a, xit, *it);
}
variable& operator=(const variable& o) {
if (this != &o) {
base::operator=(o);
x_.reset(new value_type[base_type::size() + 1]);
std::copy(o.x_.get(), o.x_.get() + base_type::size() + 1, x_.get());
if (base_type::size() != o.size()) {
using AT = std::allocator_traits<value_allocator_type>;
value_allocator_type old_alloc(base_type::get_allocator());
auto xit = x_;
const auto xend = xit + base_type::size() + 1;
while (xit != xend)
AT::destroy(old_alloc, xit++);
AT::deallocate(old_alloc, x_, base_type::size() + 1);
base::operator=(o);
value_allocator_type new_alloc(base_type::get_allocator());
x_ = AT::allocate(new_alloc, o.size() + 1);
xit = x_;
auto it = o.x_;
const auto end = o.x_ + o.size() + 1;
while (it != end)
AT::construct(new_alloc, xit++, *it++);
} else {
base::operator=(o);
std::copy(o.x_, o.x_ + o.size() + 1, x_);
}
}
return *this;
}
variable(variable&&) = default;
variable& operator=(variable&&) = default;
variable(variable&& o) : base_type(std::move(o)) {
x_ = o.x_;
o.x_ = nullptr;
}
variable& operator=(variable&& o) {
base::operator=(std::move(o));
x_ = o.x_;
o.x_ = nullptr;
return *this;
}
~variable() {
value_allocator_type a(base_type::get_allocator());
using AT = std::allocator_traits<value_allocator_type>;
auto xit = x_;
const auto end = x_ + base_type::size() + 1;
while (xit != end)
AT::destroy(a, xit++);
AT::deallocate(a, x_, base_type::size() + 1);
}
/// Returns the bin index for the passed argument.
int index(value_type x) const noexcept {
return std::upper_bound(x_.get(), x_.get() + base_type::size() + 1, x) - x_.get() - 1;
return std::upper_bound(x_, x_ + base_type::size() + 1, x) - x_ - 1;
}
/// Returns the starting edge of the bin.
@ -330,11 +384,11 @@ public:
bool operator==(const variable& o) const noexcept {
if (!base::operator==(o)) { return false; }
return std::equal(x_.get(), x_.get() + base_type::size() + 1, o.x_.get());
return std::equal(x_, x_ + base_type::size() + 1, o.x_);
}
private:
std::unique_ptr<value_type[]> x_; // smaller size compared to std::vector
value_type* x_ = nullptr;
friend class ::boost::serialization::access;
template <class Archive>

View File

@ -37,7 +37,7 @@ struct axes_equal_static_dynamic_impl {
template <typename Int>
void operator()(Int) const {
using T = mp11::mp_at<StaticAxes, Int>;
auto tp = boost::get<T>(&v[Int::value]);
auto tp = boost::relaxed_get<T>(&v[Int::value]);
equal &= (tp && *tp == std::get<Int::value>(t));
}
};

View File

@ -270,10 +270,9 @@ private:
template <typename Storage, typename... Ts>
histogram<static_axes<detail::rm_cv_ref<Ts>...>, detail::rm_cv_ref<Storage>>
make_static_histogram_with(Storage&& s, Ts&&... axis) {
using histogram_type =
histogram<static_axes<detail::rm_cv_ref<Ts>...>, detail::rm_cv_ref<Storage>>;
auto axes = typename histogram_type::axes_type(std::forward<Ts>(axis)...);
return histogram_type(std::move(axes), std::move(s));
using H = histogram<static_axes<detail::rm_cv_ref<Ts>...>, detail::rm_cv_ref<Storage>>;
auto axes = typename H::axes_type(std::forward<Ts>(axis)...);
return H(std::move(axes), std::forward<Storage>(s));
}
/// static type factory with standard storage type
@ -287,13 +286,13 @@ histogram<static_axes<detail::rm_cv_ref<Ts>...>> make_static_histogram(Ts&&... a
template <typename AnyAxisType=axis::any_std, typename Storage, typename T, typename... Ts>
histogram<mp11::mp_rename<AnyAxisType, dynamic_axes>, detail::rm_cv_ref<Storage>>
make_dynamic_histogram_with(Storage&& s, T&& axis0, Ts&&... axis) {
using histogram_type = histogram<
using H = histogram<
mp11::mp_rename<AnyAxisType, dynamic_axes>, detail::rm_cv_ref<Storage>
>;
auto a = axis0.get_allocator();
auto axes = typename histogram_type::axes_type(
auto axes = typename H::axes_type(
{AnyAxisType(std::forward<T>(axis0)), AnyAxisType(std::forward<Ts>(axis))...}, a);
return histogram_type(std::move(axes), std::move(s));
return H(std::move(axes), std::forward<Storage>(s));
}
/// dynamic type factory with standard storage type
@ -304,25 +303,29 @@ make_dynamic_histogram(T&& axis0, Ts&&... axis) {
return make_dynamic_histogram_with<AnyAxisType>(S(), std::forward<T>(axis0), std::forward<Ts>(axis)...);
}
/// dynamic type factory from axis iterators with custom storage type
/// dynamic type factory with custom storage type
template <typename Storage, typename Iterator,
typename = detail::requires_iterator<Iterator>>
histogram<typename Iterator::value_type, detail::rm_cv_ref<Storage>>
histogram<mp11::mp_rename<typename Iterator::value_type, dynamic_axes>, detail::rm_cv_ref<Storage>>
make_dynamic_histogram_with(Storage&& s, Iterator begin, Iterator end) {
BOOST_ASSERT_MSG(std::distance(begin, end) > 0, "at least one axis required");
using histogram_type = histogram<typename Iterator::value_type, detail::rm_cv_ref<Storage>>;
auto axes = typename histogram_type::axes_type(begin, end, begin->get_allocator());
return histogram_type(std::move(axes), std::move(s));
using H = histogram<mp11::mp_rename<typename Iterator::value_type, dynamic_axes>, detail::rm_cv_ref<Storage>>;
using alloc_type = typename mp11::mp_front<typename Iterator::value_type>::allocator_type;
// auto axes = typename H::axes_type(static_cast<const axis::labeled_base<alloc_type>&>(*begin).get_allocator());
// axes.reserve(std::distance(begin, end));
// while (begin != end)
// axes.emplace_back(*begin++);
// return H(std::move(axes), std::forward<Storage>(s));
return H();
}
/// dynamic type factory from axis iterators with standard storage type
template <typename Iterator, typename = detail::requires_iterator<Iterator>>
histogram<typename Iterator::value_type>
/// dynamic type factory with standard storage type
template <typename Iterator,
typename = detail::requires_iterator<Iterator>>
histogram<mp11::mp_rename<typename Iterator::value_type, dynamic_axes>>
make_dynamic_histogram(Iterator begin, Iterator end) {
using S = typename histogram<typename Iterator::value_type>::storage_type;
using S = typename histogram<mp11::mp_rename<typename Iterator::value_type, dynamic_axes>>::storage_type;
return make_dynamic_histogram_with(S(), begin, end);
}
} // namespace histogram
} // namespace boost

View File

@ -125,9 +125,23 @@ void circular<T, A>::serialize(Archive& ar, unsigned /* version */) {
template <typename T, typename A>
template <class Archive>
void variable<T, A>::serialize(Archive& ar, unsigned /* version */) {
const auto old_size = base_type::size();
value_allocator_type old_alloc = base_type::get_allocator();
ar& boost::serialization::base_object<labeled_base<A>>(*this);
if (Archive::is_loading::value) { x_.reset(new T[base::size() + 1]); }
ar& boost::serialization::make_array(x_.get(), base::size() + 1);
if (Archive::is_loading::value && old_size != base_type::size()) {
auto xit = x_;
auto xend = x_ + old_size + 1;
while (xit != xend)
old_alloc.destroy(xit++);
old_alloc.deallocate(x_, old_size + 1);
value_allocator_type new_alloc(base_type::get_allocator());
x_ = new_alloc.allocate(base_type::size() + 1);
xit = x_;
xend = x_ + base_type::size() + 1;
while (xit != xend)
new_alloc.construct(xit++);
}
ar& boost::serialization::make_array(x_, base_type::size() + 1);
}
template <typename T, typename A>

View File

@ -443,23 +443,45 @@ int main() {
// dynamic_axes with allocator
{
using inner_type = axis::integer<int, tracing_allocator<char>>;
using axis_type = axis::any<inner_type>;
using axes_type = dynamic_axes<inner_type>;
using T1 = axis::regular<axis::transform::identity, double, tracing_allocator<char>>;
using T2 = axis::circular<double, tracing_allocator<char>>;
using T3 = axis::variable<double, tracing_allocator<char>>;
using T4 = axis::integer<int, tracing_allocator<char>>;
using T5 = axis::category<int, tracing_allocator<char>>;
using axis_type = axis::any<T1, T2, T3, T4, T5>;
using axes_type = boost::mp11::mp_rename<axis_type, dynamic_axes>;
using expected = tracing_allocator<axis_type>;
BOOST_TEST_TRAIT_TRUE((std::is_same<axes_type::allocator_type, expected>));
std::size_t allocated_bytes = 0;
std::size_t deallocated_bytes = 0;
tracing_allocator_db db;
{
auto a = tracing_allocator<char>(allocated_bytes, deallocated_bytes);
auto axis = inner_type(0, 1, std::string(1024, 'c'), axis::uoflow_type::on, a);
auto a = tracing_allocator<char>(db);
const auto label = std::string(512, 'c');
axes_type axes(a);
axes.reserve(1);
axes.emplace_back(std::move(axis));
axes.reserve(5);
axes.emplace_back(T1(1, 0, 1, label, axis::uoflow_type::on, {}, a));
axes.emplace_back(T2(2, 0, T2::two_pi(), label, a));
axes.emplace_back(T3({0., 1., 2.}, label, axis::uoflow_type::on, a));
axes.emplace_back(T4(0, 4, label, axis::uoflow_type::on, a));
axes.emplace_back(T5({1, 2, 3, 4, 5}, label, axis::uoflow_type::on, a));
}
BOOST_TEST_EQ(allocated_bytes, deallocated_bytes);
BOOST_TEST_EQ(allocated_bytes, 1024 + 3 * sizeof(std::size_t) + sizeof(axis_type));
// 5 axis::any objects
BOOST_TEST_EQ(db[typeid(axis_type)].first, db[typeid(axis_type)].second);
BOOST_TEST_EQ(db[typeid(axis_type)].first, 5);
// 5 labels
BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second);
BOOST_TEST_GE(db[typeid(char)].first, 5 * 512);
// nothing to allocate for T1
// nothing to allocate for T2
// T3 allocates storage for bin edges
BOOST_TEST_EQ(db[typeid(double)].first, db[typeid(double)].second);
BOOST_TEST_EQ(db[typeid(double)].first, 3);
// nothing to allocate for T4
// T5 allocates storage for bimap
BOOST_TEST_EQ(db[typeid(boost::bimap<int, int>)].first, db[typeid(boost::bimap<int, int>)].second);
BOOST_TEST_EQ(db[typeid(boost::bimap<int, int>)].first, 1);
}
return boost::report_errors();

View File

@ -828,22 +828,28 @@ void run_tests() {
// allocator support
{
std::size_t allocated_bytes = 0, deallocated_bytes = 0;
tracing_allocator_db db;
{
tracing_allocator<char> a(allocated_bytes, deallocated_bytes);
tracing_allocator<char> a(db);
auto h = make_s(Tag(), array_storage<int, tracing_allocator<int>>(a),
axis::integer<int, tracing_allocator<char>>(
0, 1024, std::string(1024, 'c'), axis::uoflow_type::on, a));
0, 1024, std::string(512, 'c'), axis::uoflow_type::on, a));
h(0);
}
BOOST_TEST_EQ(allocated_bytes, deallocated_bytes);
BOOST_TEST_EQ(allocated_bytes,
// axis label
1024 + 3 * sizeof(std::size_t) +
// any<...>, only dynamic
sizeof(axis::integer<int, tracing_allocator<char>>) +
// array_storage
1026 * sizeof(int) + 3 * sizeof(std::size_t));
// int allocation for array_storage
BOOST_TEST_EQ(db[typeid(int)].first, db[typeid(int)].second);
BOOST_TEST_GE(db[typeid(int)].first, 1024);
// char allocation for axis label
BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second);
BOOST_TEST_GE(db[typeid(char)].first, 512);
if (Tag()) { // axis::any allocation, only for dynamic histogram
using T = axis::any<axis::integer<int, tracing_allocator<char>>>;
BOOST_TEST_EQ(db[typeid(T)].first, db[typeid(T)].second);
BOOST_TEST_EQ(db[typeid(T)].first, 1);
}
}
}

View File

@ -7,7 +7,6 @@
#ifndef BOOST_HISTOGRAM_TEST_UTILITY_HPP_
#define BOOST_HISTOGRAM_TEST_UTILITY_HPP_
#include <boost/core/typeinfo.hpp>
#include <boost/histogram/histogram.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/tuple.hpp>
@ -16,13 +15,18 @@
#include <ostream>
#include <tuple>
#include <vector>
#include <type_traits>
#include <unordered_map>
#include <typeinfo>
#include <typeindex>
using i0 = boost::mp11::mp_size_t<0>;
using i1 = boost::mp11::mp_size_t<1>;
using i2 = boost::mp11::mp_size_t<2>;
using i3 = boost::mp11::mp_size_t<3>;
namespace std { // never add to std, we only do it to get ADL working
namespace std {
// never add to std, we only do it to get ADL working :(
template <typename T>
ostream& operator<<(ostream& os, const vector<T>& v) {
os << "[ ";
@ -31,18 +35,20 @@ ostream& operator<<(ostream& os, const vector<T>& v) {
return os;
}
struct ostreamer {
ostream& os;
template <typename T>
void operator()(const T& t) const {
os << t << " ";
}
};
namespace detail {
struct ostreamer {
ostream& os;
template <typename T>
void operator()(const T& t) const {
os << t << " ";
}
};
}
template <typename... Ts>
ostream& operator<<(ostream& os, const tuple<Ts...>& t) {
os << "[ ";
::boost::mp11::tuple_for_each(t, ostreamer{os});
::boost::mp11::tuple_for_each(t, detail::ostreamer{os});
os << "]";
return os;
}
@ -55,8 +61,8 @@ typename Histogram::element_type sum(const Histogram& h) {
return std::accumulate(h.begin(), h.end(), typename Histogram::element_type(0));
}
struct static_tag {};
struct dynamic_tag {};
using static_tag = std::false_type;
using dynamic_tag = std::true_type;
template <typename... Axes>
auto make(static_tag, Axes&&... axes)
@ -82,34 +88,35 @@ auto make_s(dynamic_tag, S&& s, Axes&&... axes)
return make_dynamic_histogram_with<axis::any<detail::rm_cv_ref<Axes>...>>(s, std::forward<Axes>(axes)...);
}
using tracing_allocator_db = std::unordered_map<
std::type_index,
std::pair<std::size_t, std::size_t>
>;
template <class T>
struct tracing_allocator {
using value_type = T;
std::size_t* allocated_bytes = nullptr;
std::size_t* deallocated_bytes = nullptr;
tracing_allocator_db* db = nullptr;
tracing_allocator() noexcept {}
tracing_allocator(std::size_t& b, std::size_t& d) noexcept : allocated_bytes(&b),
deallocated_bytes(&d) {}
tracing_allocator(tracing_allocator_db& x) noexcept : db(&x) {}
template <class U>
tracing_allocator(const tracing_allocator<U>& a) noexcept
: allocated_bytes(a.allocated_bytes),
deallocated_bytes(a.deallocated_bytes) {}
: db(a.db) {}
~tracing_allocator() noexcept {}
T* allocate(std::size_t n) {
const auto& ti = BOOST_CORE_TYPEID(T);
std::cerr << "alloc " << n << " x " << boost::core::demangled_name(ti) << " = "
<< (n * sizeof(T)) << std::endl;
if (allocated_bytes) *allocated_bytes += n * sizeof(T);
if (db) {
(*db)[typeid(T)].first += n;
}
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T*& p, std::size_t n) {
const auto& ti = BOOST_CORE_TYPEID(T);
std::cerr << "dealloc " << n << " x " << boost::core::demangled_name(ti) << " = "
<< (n * sizeof(T)) << std::endl;
if (deallocated_bytes) *deallocated_bytes += n * sizeof(T);
if (db) {
(*db)[typeid(T)].second += n;
}
::operator delete((void*)p);
}
};

View File

@ -32,18 +32,17 @@ int main() {
// tracing_allocator
{
std::set<std::string> types;
std::size_t allocated_bytes = 0;
std::size_t deallocated_bytes = 0;
tracing_allocator<char> a(types, allocated_bytes, deallocated_bytes);
tracing_allocator_db db;
tracing_allocator<char> a(db);
auto p1 = a.allocate(2);
a.deallocate(p1, 2);
tracing_allocator<int> b(a);
auto p2 = b.allocate(3);
b.deallocate(p2, 3);
auto expected = {"char", "int"};
BOOST_TEST_ALL_EQ(types.begin(), types.end(), expected.begin(), expected.end());
BOOST_TEST_EQ(allocated_bytes, 2 + 3 * sizeof(int));
BOOST_TEST_EQ(allocated_bytes, deallocated_bytes);
BOOST_TEST_EQ(db.size(), 2);
BOOST_TEST_EQ(db[typeid(char)].first, 2);
BOOST_TEST_EQ(db[typeid(char)].second, 2);
BOOST_TEST_EQ(db[typeid(int)].first, 3);
BOOST_TEST_EQ(db[typeid(int)].second, 3);
}
}