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 }
0 commit comments