Skip to content

Commit 0e3f40d

Browse files
Add GrowthStrategy argument to PODVector resize and reserve (#4763)
Follow-up to #4734
1 parent 0b8294d commit 0e3f40d

File tree

4 files changed

+168
-50
lines changed

4 files changed

+168
-50
lines changed

Src/Base/AMReX_PODVector.H

Lines changed: 136 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <AMReX.H>
66
#include <AMReX_Arena.H>
7+
#include <AMReX_Enum.H>
78
#include <AMReX_GpuLaunch.H>
89
#include <AMReX_GpuAllocators.H>
910
#include <AMReX_GpuDevice.H>
@@ -244,6 +245,8 @@ namespace amrex
244245
}
245246
}
246247

248+
AMREX_ENUM(GrowthStrategy, Poisson, Exact, Geometric);
249+
247250
namespace VectorGrowthStrategy
248251
{
249252
extern AMREX_EXPORT Real growth_factor;
@@ -258,17 +261,35 @@ namespace amrex
258261
void Initialize ();
259262
}
260263

261-
inline std::size_t grow_podvector_capacity (std::size_t s)
264+
inline std::size_t grow_podvector_capacity (GrowthStrategy strategy, std::size_t new_size,
265+
std::size_t old_capacity, std::size_t sizeof_T)
262266
{
263-
if (s <= 900) {
264-
// 3*sqrt(900) = 900/10. Note that we don't need to be precise
265-
// here. Even if later we change the else block to
266-
// 4*std::sqrt(s), it's not really an issue to still use 900
267-
// here.
268-
return s + s/10;
269-
} else {
270-
return s + std::size_t(3*std::sqrt(s));
267+
switch (strategy) {
268+
case GrowthStrategy::Poisson:
269+
if (new_size <= 900) {
270+
// 3*sqrt(900) = 900/10. Note that we don't need to be precise
271+
// here. Even if later we change the else block to
272+
// 4*std::sqrt(s), it's not really an issue to still use 900
273+
// here.
274+
return new_size + new_size/10;
275+
} else {
276+
return new_size + std::size_t(3*std::sqrt(new_size));
277+
}
278+
case GrowthStrategy::Exact:
279+
return new_size;
280+
case GrowthStrategy::Geometric:
281+
if (old_capacity == 0) {
282+
return std::max(64/sizeof_T, new_size);
283+
} else {
284+
Real const gf = VectorGrowthStrategy::GetGrowthFactor();
285+
if (amrex::almostEqual(gf, Real(1.5))) {
286+
return std::max((old_capacity*3+1)/2, new_size);
287+
} else {
288+
return std::max(std::size_t(gf*Real(old_capacity+1)), new_size);
289+
}
290+
}
271291
}
292+
return 0; // unreachable
272293
}
273294

274295
template <class T, class Allocator = std::allocator<T> >
@@ -309,7 +330,8 @@ namespace amrex
309330
{}
310331

311332
explicit PODVector (size_type a_size)
312-
: m_size(a_size), m_capacity(grow_podvector_capacity(a_size))
333+
: m_size(a_size),
334+
m_capacity(grow_podvector_capacity(GrowthStrategy::Poisson, a_size, 0, sizeof(T)))
313335
{
314336
if (m_capacity != 0) {
315337
m_data = allocate(m_capacity);
@@ -322,7 +344,7 @@ namespace amrex
322344
PODVector (size_type a_size, const value_type& a_value,
323345
const allocator_type& a_allocator = Allocator())
324346
: Allocator(a_allocator), m_size(a_size),
325-
m_capacity(grow_podvector_capacity(a_size))
347+
m_capacity(grow_podvector_capacity(GrowthStrategy::Poisson, a_size, 0, sizeof(T)))
326348
{
327349
if (m_capacity != 0) {
328350
m_data = allocate(m_capacity);
@@ -337,7 +359,8 @@ namespace amrex
337359
const allocator_type& a_allocator = Allocator())
338360
: Allocator(a_allocator),
339361
m_size (a_initializer_list.size()),
340-
m_capacity(grow_podvector_capacity(a_initializer_list.size()))
362+
m_capacity(grow_podvector_capacity(
363+
GrowthStrategy::Poisson, a_initializer_list.size(), 0, sizeof(T)))
341364
{
342365
if (m_capacity != 0) {
343366
m_data = allocate(m_capacity);
@@ -661,20 +684,82 @@ namespace amrex
661684

662685
[[nodiscard]] const_reverse_iterator crend () const noexcept { return const_reverse_iterator(begin()); }
663686

664-
void resize (size_type a_new_size)
687+
/** Resizes the PODVector to a_new_size elements. New elements are either uninitialized or,
688+
* if amrex.init_snan is set to true, initialized to NaN.
689+
* To minimize reallocations, the capacity of the vector may be increased to a value larger
690+
* than the requested size. This behavior can be controlled using the strategy parameter.
691+
* Available strategies are:
692+
*
693+
* - GrowthStrategy::Poisson (Default)
694+
* Sets the new capacity to a_new_size + 3*sqrt(a_new_size).
695+
* This is useful for vectors that store particle data, with particles moving between
696+
* tiles due to a thermal process. In this case the expected number of particles
697+
* per tile follows a Poisson distribution, which has a standard deviation of sqrt(n).
698+
* Adding three sigma to the size greatly minimizes reallocations in Redistribute()
699+
* of successive timesteps.
700+
*
701+
* - GrowthStrategy::Exact
702+
* Sets the new capacity exactly to the requested size.
703+
* Can be used if the PODVector will never be resized to a bigger size.
704+
*
705+
* - GrowthStrategy::Geometric
706+
* Sets the new capacity to max(growth_factor * old_capacity, a_new_size),
707+
* where growth_factor is 1.5 by default and can be adjusted with
708+
* amrex.vector_growth_factor. This strategy is needed to guarantee O(n) runtime
709+
* if the PODVector is resized in a loop to consecutively increasing sizes up to n.
710+
* In contrast, the Poisson and Exact strategies would have O(n*sqrt(n)) and O(n^2)
711+
* runtimes, respectively. However, it is not recommended to use a PODVector this way.
712+
* Instead, the reserve function should be used once upfront.
713+
*
714+
* \param[in] a_new_size the new size of the PODVector
715+
* \param[in] strategy the growth strategy to set the new capacity
716+
*/
717+
void resize (size_type a_new_size,
718+
GrowthStrategy strategy = GrowthStrategy::Poisson)
665719
{
666720
auto old_size = m_size;
667-
resize_without_init_snan(a_new_size);
721+
resize_without_init_snan(a_new_size, strategy);
668722
if (old_size < a_new_size) {
669723
detail::maybe_init_snan(m_data + old_size,
670724
m_size - old_size, (Allocator const&)(*this));
671725
}
672726
}
673727

674-
void resize (size_type a_new_size, const T& a_val)
728+
/** Resizes the PODVector to a_new_size elements. New elements are set to a_val.
729+
* To minimize reallocations, the capacity of the vector may be increased to a value larger
730+
* than the requested size. This behavior can be controlled using the strategy parameter.
731+
* Available strategies are:
732+
*
733+
* - GrowthStrategy::Poisson (Default)
734+
* Sets the new capacity to a_new_size + 3*sqrt(a_new_size).
735+
* This is useful for vectors that store particle data, with particles moving between
736+
* tiles due to a thermal process. In this case the expected number of particles
737+
* per tile follows a Poisson distribution, which has a standard deviation of sqrt(n).
738+
* Adding three sigma to the size greatly minimizes reallocations in Redistribute()
739+
* of successive timesteps.
740+
*
741+
* - GrowthStrategy::Exact
742+
* Sets the new capacity exactly to the requested size.
743+
* Can be used if the PODVector will never be resized to a bigger size.
744+
*
745+
* - GrowthStrategy::Geometric
746+
* Sets the new capacity to max(growth_factor * old_capacity, a_new_size),
747+
* where growth_factor is 1.5 by default and can be adjusted with
748+
* amrex.vector_growth_factor. This strategy is needed to guarantee O(n) runtime
749+
* if the PODVector is resized in a loop to consecutively increasing sizes up to n.
750+
* In contrast, the Poisson and Exact strategies would have O(n*sqrt(n)) and O(n^2)
751+
* runtimes, respectively. However, it is not recommended to use a PODVector this way.
752+
* Instead, the reserve function should be used once upfront.
753+
*
754+
* \param[in] a_new_size the new size of the PODVector
755+
* \param[in] a_val the value of the new elements
756+
* \param[in] strategy the growth strategy to set the new capacity
757+
*/
758+
void resize (size_type a_new_size, const T& a_val,
759+
GrowthStrategy strategy = GrowthStrategy::Poisson)
675760
{
676761
size_type old_size = m_size;
677-
resize_without_init_snan(a_new_size);
762+
resize_without_init_snan(a_new_size, strategy);
678763
if (old_size < a_new_size)
679764
{
680765
detail::uninitializedFillNImpl(m_data + old_size,
@@ -683,10 +768,39 @@ namespace amrex
683768
}
684769
}
685770

686-
void reserve (size_type a_capacity)
771+
/** Sets the capacity of the PODVector to at least a_capacity without changing the size.
772+
* To minimize reallocations, the capacity of the vector may be increased to a value larger
773+
* than the requested one. This behavior can be controlled using the strategy parameter.
774+
* Available strategies are:
775+
*
776+
* - GrowthStrategy::Poisson (Default)
777+
* Sets the new capacity to a_capacity + 3*sqrt(a_capacity).
778+
* This is useful for vectors that store particle data, with particles moving between
779+
* tiles due to a thermal process. In this case the expected number of particles
780+
* per tile follows a Poisson distribution, which has a standard deviation of sqrt(n).
781+
* Adding three sigma to the size greatly minimizes reallocations in Redistribute()
782+
* of successive timesteps.
783+
*
784+
* - GrowthStrategy::Exact
785+
* Sets the new capacity exactly to the requested a_capacity.
786+
* Can be used if the PODVector will never be resized to a bigger size.
787+
*
788+
* - GrowthStrategy::Geometric
789+
* Sets the new capacity to max(growth_factor * old_capacity, a_capacity),
790+
* where growth_factor is 1.5 by default and can be adjusted with
791+
* amrex.vector_growth_factor. This strategy is needed to guarantee O(n) runtime
792+
* if the PODVector is resized in a loop to consecutively increasing sizes up to n.
793+
* In contrast, the Poisson and Exact strategies would have O(n*sqrt(n)) and O(n^2)
794+
* runtimes, respectively. However, it is not recommended to use a PODVector this way.
795+
* Instead, the reserve function should be used once upfront.
796+
*
797+
* \param[in] a_capacity the new minimum capacity of the PODVector
798+
* \param[in] strategy the growth strategy to set the new capacity
799+
*/
800+
void reserve (size_type a_capacity, GrowthStrategy strategy = GrowthStrategy::Poisson)
687801
{
688802
if (m_capacity < a_capacity) {
689-
reserve_doit(grow_podvector_capacity(a_capacity));
803+
reserve_doit(grow_podvector_capacity(strategy, a_capacity, m_capacity, sizeof(T)));
690804
}
691805
}
692806

@@ -738,16 +852,8 @@ namespace amrex
738852
// this is where we would change the growth strategy for push_back
739853
[[nodiscard]] size_type GetNewCapacityForPush () const noexcept
740854
{
741-
if (m_capacity == 0) {
742-
return std::max(64/sizeof(T), size_type(1));
743-
} else {
744-
Real const gf = VectorGrowthStrategy::GetGrowthFactor();
745-
if (amrex::almostEqual(gf, Real(1.5))) {
746-
return (m_capacity*3+1)/2;
747-
} else {
748-
return size_type(gf*Real(m_capacity+1));
749-
}
750-
}
855+
return grow_podvector_capacity(GrowthStrategy::Geometric, m_capacity + 1,
856+
m_capacity, sizeof(T));
751857
}
752858

753859
void UpdateDataPtr (FatPtr<T> const& fp)
@@ -817,10 +923,10 @@ namespace amrex
817923
m_capacity = new_capacity;
818924
}
819925

820-
void resize_without_init_snan (size_type a_new_size)
926+
void resize_without_init_snan (size_type a_new_size, GrowthStrategy strategy)
821927
{
822928
if (m_capacity < a_new_size) {
823-
reserve(a_new_size);
929+
reserve(a_new_size, strategy);
824930
}
825931
m_size = a_new_size;
826932
}

Src/Particle/AMReX_ArrayOfStructs.H

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,13 @@ public:
9191
m_data.swap(other.m_data);
9292
}
9393

94-
void resize (size_t count) { m_data.resize(count); }
94+
void resize (size_t count, GrowthStrategy strategy = GrowthStrategy::Poisson) {
95+
m_data.resize(count, strategy);
96+
}
9597

96-
void reserve (size_t capacity) { m_data.reserve(capacity); }
98+
void reserve (size_t capacity, GrowthStrategy strategy = GrowthStrategy::Poisson) {
99+
m_data.reserve(capacity, strategy);
100+
}
97101

98102
Iterator erase ( ConstIterator first, ConstIterator second) { return m_data.erase(first, second); }
99103

Src/Particle/AMReX_ParticleTile.H

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -965,20 +965,20 @@ struct ParticleTile
965965
}
966966
}
967967

968-
void resize (std::size_t count)
968+
void resize (std::size_t count, GrowthStrategy strategy = GrowthStrategy::Poisson)
969969
{
970970
if constexpr (!ParticleType::is_soa_particle) {
971-
m_aos_tile.resize(count);
971+
m_aos_tile.resize(count, strategy);
972972
}
973-
m_soa_tile.resize(count);
973+
m_soa_tile.resize(count, strategy);
974974
}
975975

976-
void reserve (std::size_t capacity)
976+
void reserve (std::size_t capacity, GrowthStrategy strategy = GrowthStrategy::Poisson)
977977
{
978978
if constexpr (!ParticleType::is_soa_particle) {
979-
m_aos_tile.reserve(capacity);
979+
m_aos_tile.reserve(capacity, strategy);
980980
}
981-
m_soa_tile.reserve(capacity);
981+
m_soa_tile.reserve(capacity, strategy);
982982
}
983983

984984
///

Src/Particle/AMReX_StructOfArrays.H

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,34 +267,42 @@ struct StructOfArrays {
267267

268268
[[nodiscard]] int getNumNeighbors () const { return m_num_neighbor_particles; }
269269

270-
void resize (size_t count)
270+
void resize (size_t count, GrowthStrategy strategy = GrowthStrategy::Poisson)
271271
{
272272
if constexpr (use64BitIdCpu == true) {
273-
m_idcpu.resize(count);
273+
m_idcpu.resize(count, strategy);
274274
}
275275
if constexpr (NReal > 0) {
276-
for (int i = 0; i < NReal; ++i) { m_rdata[i].resize(count); }
276+
for (int i = 0; i < NReal; ++i) { m_rdata[i].resize(count, strategy); }
277277
}
278278
if constexpr (NInt > 0) {
279-
for (int i = 0; i < NInt; ++i) { m_idata[i].resize(count); }
279+
for (int i = 0; i < NInt; ++i) { m_idata[i].resize(count, strategy); }
280+
}
281+
for (int i = 0; i < int(m_runtime_rdata.size()); ++i) {
282+
m_runtime_rdata[i].resize(count, strategy);
283+
}
284+
for (int i = 0; i < int(m_runtime_idata.size()); ++i) {
285+
m_runtime_idata[i].resize(count, strategy);
280286
}
281-
for (int i = 0; i < int(m_runtime_rdata.size()); ++i) { m_runtime_rdata[i].resize(count); }
282-
for (int i = 0; i < int(m_runtime_idata.size()); ++i) { m_runtime_idata[i].resize(count); }
283287
}
284288

285-
void reserve (size_t capacity)
289+
void reserve (size_t capacity, GrowthStrategy strategy = GrowthStrategy::Poisson)
286290
{
287291
if constexpr (use64BitIdCpu == true) {
288-
m_idcpu.reserve(capacity);
292+
m_idcpu.reserve(capacity, strategy);
289293
}
290294
if constexpr (NReal > 0) {
291-
for (int i = 0; i < NReal; ++i) { m_rdata[i].reserve(capacity); }
295+
for (int i = 0; i < NReal; ++i) { m_rdata[i].reserve(capacity, strategy); }
292296
}
293297
if constexpr (NInt > 0) {
294-
for (int i = 0; i < NInt; ++i) { m_idata[i].reserve(capacity); }
298+
for (int i = 0; i < NInt; ++i) { m_idata[i].reserve(capacity, strategy); }
299+
}
300+
for (int i = 0; i < int(m_runtime_rdata.size()); ++i) {
301+
m_runtime_rdata[i].reserve(capacity, strategy);
302+
}
303+
for (int i = 0; i < int(m_runtime_idata.size()); ++i) {
304+
m_runtime_idata[i].reserve(capacity, strategy);
295305
}
296-
for (int i = 0; i < int(m_runtime_rdata.size()); ++i) { m_runtime_rdata[i].reserve(capacity); }
297-
for (int i = 0; i < int(m_runtime_idata.size()); ++i) { m_runtime_idata[i].reserve(capacity); }
298306
}
299307

300308
[[nodiscard]] uint64_t* idcpuarray () {

0 commit comments

Comments
 (0)