Skip to content

Commit 390d6bb

Browse files
Avoid temporary arrays for independent numeric generators
1 parent 57f3d07 commit 390d6bb

6 files changed

Lines changed: 152 additions & 1 deletion

File tree

‎docs/source/builder.rst‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ Numerical ranges
2626
- :cpp:func:`xt::linspace(start, stop, num_samples) <xt::linspace>`: generates num_samples evenly spaced numbers over given interval.
2727
- :cpp:func:`xt::logspace(start, stop, num_samples) <xt::logspace>`: generates num_samples evenly spaced on a log scale over given interval
2828

29+
Assignment of independent generators
30+
-----------------------------------
31+
32+
Assigning ``arange`` or ``linspace`` with arithmetic types to a contiguous
33+
container reuses its storage when the size is unchanged, without creating a
34+
temporary array. Views retain their existing assignment path, which can benefit
35+
from generating into a contiguous temporary first.
36+
37+
Custom generator functors conservatively retain overlap protection. A functor
38+
which cannot read array storage can opt in by specializing
39+
``xt::is_memory_independent<Functor>`` to inherit from ``std::true_type``.
40+
``xgenerator`` forwards this trait from its functor, including reference-qualified
41+
functors. Do not opt in for functors that read other expressions, since those
42+
expressions can refer to the assignment destination. The option
43+
``XTENSOR_FORCE_TEMPORARY_MEMORY_IN_ASSIGNMENTS`` still forces temporary evaluation.
44+
2945
Joining expressions
3046
-------------------
3147

‎include/xtensor/core/xsemantic.hpp‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,20 @@ namespace xt
682682
template <class E>
683683
inline auto xcontainer_semantic<D>::operator=(const xexpression<E>& e) -> derived_type&
684684
{
685-
return base_type::operator=(e);
685+
#ifndef XTENSOR_FORCE_TEMPORARY_MEMORY_IN_ASSIGNMENTS
686+
// Keep this optimization on containers: a view may benefit from first
687+
// generating into a contiguous temporary with a specialized assign_to.
688+
if constexpr (is_memory_independent<E>::value && D::contiguous_layout)
689+
{
690+
// Validate the shape before entering a generator's noexcept assign_to.
691+
this->derived_cast().resize(e.derived_cast().shape());
692+
return this->assign(e);
693+
}
694+
else
695+
#endif
696+
{
697+
return base_type::operator=(e);
698+
}
686699
}
687700

688701
/*********************************

‎include/xtensor/generators/xbuilder.hpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,12 @@ namespace xt
390390
};
391391
}
392392

393+
template <class T, class R, class S>
394+
struct is_memory_independent<detail::arange_generator<T, R, S>>
395+
: std::conjunction<std::is_arithmetic<T>, std::is_arithmetic<R>, std::is_arithmetic<S>>
396+
{
397+
};
398+
393399
/**
394400
* Generates an array with ones on the diagonal.
395401
* @param shape shape of the resulting expression

‎include/xtensor/generators/xgenerator.hpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ namespace xt
5858
template <class F, class R, class S>
5959
class xgenerator;
6060

61+
template <class F, class R, class S>
62+
struct is_memory_independent<xgenerator<F, R, S>> : is_memory_independent<std::decay_t<F>>
63+
{
64+
};
65+
6166
template <typename T>
6267
concept xgenerator_concept = is_specialization_of<xgenerator, std::decay_t<T>>::value;
6368

‎include/xtensor/utils/xutils.hpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,16 @@ namespace xt
672672
return !(a == b);
673673
}
674674

675+
/**
676+
* Whether evaluating a functor or expression is independent of array storage.
677+
* Specialize only for types which cannot read from an assignment destination.
678+
* Unknown types conservatively retain overlap protection.
679+
*/
680+
template <class T>
681+
struct is_memory_independent : std::false_type
682+
{
683+
};
684+
675685
/*****************
676686
* assignable_to *
677687
******************/

‎test/test_xassign.cpp‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,66 @@
88
****************************************************************************/
99

1010
#include <iterator>
11+
#include <memory>
1112
#include <type_traits>
1213
#include <vector>
1314

1415
#include "xtensor/containers/xarray.hpp"
1516
#include "xtensor/containers/xtensor.hpp"
1617
#include "xtensor/core/xassign.hpp"
1718
#include "xtensor/core/xnoalias.hpp"
19+
#include "xtensor/generators/xbuilder.hpp"
1820
#include "xtensor/misc/xmanipulation.hpp"
1921

2022
#include "test_common.hpp"
2123
#include "test_common_macros.hpp"
2224

25+
namespace
26+
{
27+
template <class T>
28+
struct counting_allocator : std::allocator<T>
29+
{
30+
using value_type = T;
31+
inline static std::size_t allocations = 0;
32+
33+
template <class U>
34+
struct rebind
35+
{
36+
using other = counting_allocator<U>;
37+
};
38+
39+
counting_allocator() = default;
40+
41+
template <class U>
42+
counting_allocator(const counting_allocator<U>&)
43+
{
44+
}
45+
46+
T* allocate(std::size_t n)
47+
{
48+
++allocations;
49+
return std::allocator<T>::allocate(n);
50+
}
51+
};
52+
53+
struct reverse_reader
54+
{
55+
using value_type = int;
56+
const xt::xarray<int>& source;
57+
58+
int operator()(std::size_t i) const
59+
{
60+
return source(source.size() - 1 - i);
61+
}
62+
63+
template <class It>
64+
int element(It first, It) const
65+
{
66+
return (*this)(*first);
67+
}
68+
};
69+
}
70+
2371
// a dummy shape *not derived* from std::vector but compatible
2472
template <class T>
2573
class my_vector
@@ -137,6 +185,57 @@ class my_vector
137185
namespace xt
138186
{
139187

188+
#ifndef XTENSOR_FORCE_TEMPORARY_MEMORY_IN_ASSIGNMENTS
189+
TEST(xassign, independent_generator_reuses_storage)
190+
{
191+
using allocator = counting_allocator<double>;
192+
using array = xtensor<double, 1, layout_type::row_major, allocator>;
193+
array a = array::from_shape({16});
194+
195+
allocator::allocations = 0;
196+
a = arange<double>(16);
197+
EXPECT_EQ(allocator::allocations, 0u);
198+
for (std::size_t i = 0; i < a.size(); ++i)
199+
{
200+
EXPECT_EQ(a(i), double(i));
201+
}
202+
203+
allocator::allocations = 0;
204+
a = linspace<double>(0., 15., 16);
205+
EXPECT_EQ(allocator::allocations, 0u);
206+
for (std::size_t i = 0; i < a.size(); ++i)
207+
{
208+
EXPECT_EQ(a(i), double(i));
209+
}
210+
}
211+
#endif
212+
213+
TEST(xassign, independent_generator_resizes)
214+
{
215+
xarray<double> a = {{-1., -1.}, {-1., -1.}};
216+
a = arange<double>(5);
217+
EXPECT_EQ(a.dimension(), 1u);
218+
EXPECT_EQ(a.size(), 5u);
219+
EXPECT_EQ(a(4), 4.);
220+
221+
a = arange<double>(0);
222+
EXPECT_EQ(a.size(), 0u);
223+
a = linspace<double>(2., 4., 3);
224+
EXPECT_EQ(a, (xarray<double>{2., 3., 4.}));
225+
}
226+
227+
TEST(xassign, reading_generators_keep_overlap_protection)
228+
{
229+
xarray<int> a = {1, 2, 3, 4};
230+
auto reverse = detail::make_xgenerator(reverse_reader{a}, {4});
231+
static_assert(!is_memory_independent<decltype(reverse)>::value);
232+
a = reverse;
233+
EXPECT_EQ(a, (xarray<int>{4, 3, 2, 1}));
234+
235+
a = concatenate(xtuple(a, a));
236+
EXPECT_EQ(a, (xarray<int>{4, 3, 2, 1, 4, 3, 2, 1}));
237+
}
238+
140239
template <class T, class C_T>
141240
struct rebind_container<T, my_vector<C_T>>
142241
{
@@ -173,5 +272,7 @@ namespace xt
173272
{
174273
auto a = xt::xtensor<float, 1>::from_shape({2});
175274
XT_ASSERT_THROW(a = xt::expand_dims(a, 0), std::runtime_error);
275+
auto b = xt::xtensor<float, 2>::from_shape({2, 2});
276+
XT_ASSERT_THROW(b = arange<float>(4), std::runtime_error);
176277
}
177278
}

0 commit comments

Comments
 (0)