@@ -76,6 +76,27 @@ class CowData {
7676
7777 // internal helpers
7878
79+ static constexpr _FORCE_INLINE_ USize grow_capacity (USize p_previous_capacity) {
80+ // 1.5x the given size.
81+ // This ratio was chosen because it is close to the ideal growth rate of the golden ratio.
82+ // See https://archive.ph/Z2R8w for details.
83+ return MAX ((USize)2 , p_previous_capacity + ((1 + p_previous_capacity) >> 1 ));
84+ }
85+
86+ static constexpr _FORCE_INLINE_ USize next_capacity (USize p_previous_capacity, USize p_size) {
87+ if (p_previous_capacity < p_size) {
88+ return MAX (grow_capacity (p_previous_capacity), p_size);
89+ }
90+ return p_previous_capacity;
91+ }
92+
93+ static constexpr _FORCE_INLINE_ USize smaller_capacity (USize p_previous_capacity, USize p_size) {
94+ if (p_size < p_previous_capacity >> 2 ) {
95+ return grow_capacity (p_size);
96+ }
97+ return p_previous_capacity;
98+ }
99+
79100 static _FORCE_INLINE_ T *_get_data_ptr (uint8_t *p_ptr) {
80101 return (T *)(p_ptr + DATA_OFFSET);
81102 }
@@ -95,34 +116,6 @@ class CowData {
95116 return (USize *)((uint8_t *)_ptr - DATA_OFFSET + CAPACITY_OFFSET);
96117 }
97118
98- _FORCE_INLINE_ static USize _get_alloc_size (USize p_elements) {
99- return next_power_of_2 (p_elements * (USize)sizeof (T));
100- }
101-
102- _FORCE_INLINE_ static bool _get_alloc_size_checked (USize p_elements, USize *out) {
103- if (unlikely (p_elements == 0 )) {
104- *out = 0 ;
105- return true ;
106- }
107- #if defined(__GNUC__) && defined(IS_32_BIT)
108- USize o;
109- USize p;
110- if (__builtin_mul_overflow (p_elements, sizeof (T), &o)) {
111- *out = 0 ;
112- return false ;
113- }
114- *out = next_power_of_2 (o);
115- if (__builtin_add_overflow (o, static_cast <USize>(32 ), &p)) {
116- return false ; // No longer allocated here.
117- }
118- #else
119- // Speed is more important than correctness here, do the operations unchecked
120- // and hope for the best.
121- *out = _get_alloc_size (p_elements);
122- #endif
123- return *out;
124- }
125-
126119 // Decrements the reference count. Deallocates the backing buffer if needed.
127120 // After this function, _ptr is guaranteed to be NULL.
128121 void _unref ();
@@ -133,22 +126,21 @@ class CowData {
133126 // / It is the responsibility of the caller to:
134127 // / - Ensure _ptr == nullptr
135128 // / - Ensure p_capacity > 0
136- Error _alloc (USize p_capacity);
129+ Error _alloc_exact (USize p_capacity);
137130
138131 // / Re-allocates the backing array to the given capacity.
139132 // / It is the responsibility of the caller to:
140133 // / - Ensure we are the only owner of the backing array
141134 // / - Ensure p_capacity > 0
142- Error _realloc (USize p_capacity);
143- Error _realloc_bytes (USize p_bytes);
135+ Error _realloc_exact (USize p_capacity);
144136
145137 // / Create a new buffer and copies over elements from the old buffer.
146138 // / Elements are inserted first from the start, then a gap is left uninitialized, and then elements are inserted from the back.
147139 // / It is the responsibility of the caller to:
148140 // / - Construct elements in the gap.
149141 // / - Ensure size() >= p_size_from_start and size() >= p_size_from_back.
150- // / - Ensure p_min_capacity is enough to hold all elements.
151- [[nodiscard]] Error _copy_to_new_buffer (USize p_min_capacity , USize p_size_from_start, USize p_gap, USize p_size_from_back);
142+ // / - Ensure p_capacity is enough to hold all elements.
143+ [[nodiscard]] Error _copy_to_new_buffer_exact (USize p_capacity , USize p_size_from_start, USize p_gap, USize p_size_from_back);
152144
153145 // / Ensure we are the only owners of the backing buffer.
154146 [[nodiscard]] Error _copy_on_write ();
@@ -206,7 +198,11 @@ class CowData {
206198 template <bool p_init = false >
207199 Error resize (Size p_size);
208200
201+ template <bool p_exact = false >
209202 Error reserve (USize p_min_capacity);
203+ _FORCE_INLINE_ Error reserve_exact (USize p_capacity) {
204+ return reserve<true >(p_capacity);
205+ }
210206
211207 _FORCE_INLINE_ void remove_at (Size p_index);
212208
@@ -285,18 +281,16 @@ void CowData<T>::remove_at(Size p_index) {
285281 _ptr[p_index].~T ();
286282 memmove ((void *)(_ptr + p_index), (void *)(_ptr + p_index + 1 ), (new_size - p_index) * sizeof (T));
287283
288- // Shrink buffer if necessary.
289- const USize new_alloc_size = _get_alloc_size (new_size);
290- const USize prev_alloc_size = _get_alloc_size (capacity ());
291- if (new_alloc_size < prev_alloc_size) {
292- Error err = _realloc_bytes (new_alloc_size);
284+ // Shrink to fit if necessary.
285+ const USize new_capacity = smaller_capacity (capacity (), new_size);
286+ if (new_capacity < capacity ()) {
287+ Error err = _realloc_exact (new_capacity);
293288 CRASH_COND (err);
294289 }
295-
296290 *_get_size () = new_size;
297291 } else {
298292 // Remove by forking.
299- Error err = _copy_to_new_buffer ( new_size, p_index, 0 , new_size - p_index);
293+ Error err = _copy_to_new_buffer_exact ( smaller_capacity ( capacity (), new_size) , p_index, 0 , new_size - p_index);
300294 CRASH_COND (err);
301295 }
302296}
@@ -307,12 +301,12 @@ Error CowData<T>::insert(Size p_pos, const T &p_val) {
307301 ERR_FAIL_INDEX_V (p_pos, new_size, ERR_INVALID_PARAMETER);
308302
309303 if (!_ptr) {
310- _alloc ( 1 );
304+ _alloc_exact ( next_capacity ( 0 , 1 ) );
311305 *_get_size () = 1 ;
312306 } else if (_get_refcount ()->get () == 1 ) {
313307 if ((USize)new_size > capacity ()) {
314308 // Need to grow.
315- const Error error = _realloc (new_size );
309+ const Error error = _realloc_exact ( grow_capacity ( capacity ()) );
316310 if (error) {
317311 return error;
318312 }
@@ -324,8 +318,8 @@ Error CowData<T>::insert(Size p_pos, const T &p_val) {
324318 } else {
325319 // Insert new element by forking.
326320 // Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve.
327- const USize new_capacity = MAX (capacity (), (USize) new_size);
328- const Error error = _copy_to_new_buffer (new_capacity, p_pos, 1 , size () - p_pos);
321+ const USize new_capacity = next_capacity (capacity (), new_size);
322+ const Error error = _copy_to_new_buffer_exact (new_capacity, p_pos, 1 , size () - p_pos);
329323 if (error) {
330324 return error;
331325 }
@@ -343,13 +337,13 @@ Error CowData<T>::push_back(const T &p_val) {
343337
344338 if (!_ptr) {
345339 // Grow by allocating.
346- _alloc ( 1 );
340+ _alloc_exact ( next_capacity ( 0 , 1 ) );
347341 *_get_size () = 1 ;
348342 } else if (_get_refcount ()->get () == 1 ) {
349343 // Grow in-place.
350344 if ((USize)new_size > capacity ()) {
351345 // Need to grow.
352- const Error error = _realloc (new_size );
346+ const Error error = _realloc_exact ( grow_capacity ( capacity ()) );
353347 if (error) {
354348 return error;
355349 }
@@ -359,8 +353,8 @@ Error CowData<T>::push_back(const T &p_val) {
359353 } else {
360354 // Grow by forking.
361355 // Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve.
362- const USize new_capacity = MAX (capacity (), (USize) new_size);
363- const Error error = _copy_to_new_buffer (new_capacity, size (), 1 , 0 );
356+ const USize new_capacity = next_capacity (capacity (), new_size);
357+ const Error error = _copy_to_new_buffer_exact (new_capacity, size (), 1 , 0 );
364358 if (error) {
365359 return error;
366360 }
@@ -373,25 +367,26 @@ Error CowData<T>::push_back(const T &p_val) {
373367}
374368
375369template <typename T>
370+ template <bool p_exact>
376371Error CowData<T>::reserve(USize p_min_capacity) {
377- if (p_min_capacity <= capacity ()) {
372+ USize new_capacity = p_exact ? p_min_capacity : next_capacity (capacity (), p_min_capacity);
373+ if (new_capacity <= capacity ()) {
378374 if (p_min_capacity < (USize)size ()) {
379375 WARN_VERBOSE (" reserve() called with a capacity smaller than the current size. This is likely a mistake." );
380376 }
381-
382377 // No need to reserve more, we already have (at least) the right size.
383378 return OK;
384379 }
385380
386381 if (!_ptr) {
387382 // Initial allocation.
388- return _alloc (p_min_capacity );
383+ return _alloc_exact (new_capacity );
389384 } else if (_get_refcount ()->get () == 1 ) {
390385 // Grow in-place.
391- return _realloc (p_min_capacity );
386+ return _realloc_exact (new_capacity );
392387 } else {
393388 // Grow by forking.
394- return _copy_to_new_buffer (p_min_capacity , size (), 0 , 0 );
389+ return _copy_to_new_buffer_exact (new_capacity , size (), 0 , 0 );
395390 }
396391}
397392
@@ -411,21 +406,21 @@ Error CowData<T>::resize(Size p_size) {
411406
412407 if (!_ptr) {
413408 // Grow by allocating.
414- const Error error = _alloc ( p_size);
409+ const Error error = _alloc_exact ( next_capacity ( 0 , p_size) );
415410 if (error) {
416411 return error;
417412 }
418413 } else if (_get_refcount ()->get () == 1 ) {
419414 // Grow in-place.
420415 if ((USize)p_size > capacity ()) {
421- const Error error = _realloc ( p_size);
416+ const Error error = _realloc_exact ( next_capacity ( capacity (), p_size) );
422417 if (error) {
423418 return error;
424419 }
425420 }
426421 } else {
427422 // Grow by forking.
428- const Error error = _copy_to_new_buffer ( p_size, prev_size, 0 , 0 );
423+ const Error error = _copy_to_new_buffer_exact ( next_capacity ( capacity (), p_size) , prev_size, 0 , 0 );
429424 if (error) {
430425 return error;
431426 }
@@ -449,30 +444,27 @@ Error CowData<T>::resize(Size p_size) {
449444 destruct_arr_placement (_ptr + p_size, prev_size - p_size);
450445
451446 // Shrink buffer if necessary.
452- const USize new_alloc_size = _get_alloc_size (p_size);
453- const USize prev_alloc_size = _get_alloc_size (capacity ());
454- if (new_alloc_size < prev_alloc_size) {
455- Error err = _realloc_bytes (new_alloc_size);
447+ const USize new_capacity = smaller_capacity (capacity (), p_size);
448+ if (new_capacity < capacity ()) {
449+ Error err = _realloc_exact (new_capacity);
456450 CRASH_COND (err);
457451 }
458452
459453 *_get_size () = p_size;
460454 return OK;
461455 } else {
462456 // Shrink by forking.
463- return _copy_to_new_buffer (p_size, p_size, 0 , 0 );
457+ const USize new_capacity = smaller_capacity (capacity (), p_size);
458+ return _copy_to_new_buffer_exact (new_capacity, p_size, 0 , 0 );
464459 }
465460 }
466461}
467462
468463template <typename T>
469- Error CowData<T>::_alloc (USize p_min_capacity ) {
464+ Error CowData<T>::_alloc_exact (USize p_capacity ) {
470465 DEV_ASSERT (!_ptr);
471466
472- USize alloc_size;
473- ERR_FAIL_COND_V (!_get_alloc_size_checked (p_min_capacity, &alloc_size), ERR_OUT_OF_MEMORY);
474-
475- uint8_t *mem_new = (uint8_t *)Memory::alloc_static (alloc_size + DATA_OFFSET, false );
467+ uint8_t *mem_new = (uint8_t *)Memory::alloc_static (p_capacity * sizeof (T) + DATA_OFFSET, false );
476468 ERR_FAIL_NULL_V (mem_new, ERR_OUT_OF_MEMORY);
477469
478470 _ptr = _get_data_ptr (mem_new);
@@ -481,23 +473,16 @@ Error CowData<T>::_alloc(USize p_min_capacity) {
481473 new (_get_refcount ()) SafeNumeric<USize>(1 );
482474 *_get_size () = 0 ;
483475 // The actual capacity is whatever we can stuff into the alloc_size.
484- *_get_capacity () = alloc_size / sizeof (T) ;
476+ *_get_capacity () = p_capacity ;
485477
486478 return OK;
487479}
488480
489481template <typename T>
490- Error CowData<T>::_realloc(USize p_min_capacity) {
491- USize bytes;
492- ERR_FAIL_COND_V (!_get_alloc_size_checked (p_min_capacity, &bytes), ERR_OUT_OF_MEMORY);
493- return _realloc_bytes (bytes);
494- }
495-
496- template <typename T>
497- Error CowData<T>::_realloc_bytes(USize p_bytes) {
482+ Error CowData<T>::_realloc_exact(USize p_capacity) {
498483 DEV_ASSERT (_ptr);
499484
500- uint8_t *mem_new = (uint8_t *)Memory::realloc_static (((uint8_t *)_ptr) - DATA_OFFSET, p_bytes + DATA_OFFSET, false );
485+ uint8_t *mem_new = (uint8_t *)Memory::realloc_static (((uint8_t *)_ptr) - DATA_OFFSET, p_capacity * sizeof (T) + DATA_OFFSET, false );
501486 ERR_FAIL_NULL_V (mem_new, ERR_OUT_OF_MEMORY);
502487
503488 _ptr = _get_data_ptr (mem_new);
@@ -507,14 +492,14 @@ Error CowData<T>::_realloc_bytes(USize p_bytes) {
507492 DEV_ASSERT (_get_refcount ()->get () == 1 );
508493 // The size was also copied from the previous allocation.
509494 // The actual capacity is whatever we can stuff into the alloc_size.
510- *_get_capacity () = p_bytes / sizeof (T) ;
495+ *_get_capacity () = p_capacity ;
511496
512497 return OK;
513498}
514499
515500template <typename T>
516- Error CowData<T>::_copy_to_new_buffer (USize p_min_capacity , USize p_size_from_start, USize p_gap, USize p_size_from_back) {
517- DEV_ASSERT (p_min_capacity >= p_size_from_start + p_size_from_back + p_gap);
501+ Error CowData<T>::_copy_to_new_buffer_exact (USize p_capacity , USize p_size_from_start, USize p_gap, USize p_size_from_back) {
502+ DEV_ASSERT (p_capacity >= p_size_from_start + p_size_from_back + p_gap);
518503 DEV_ASSERT ((USize)size () >= p_size_from_start && (USize)size () >= p_size_from_back);
519504
520505 // Create a temporary CowData to hold ownership over our _ptr.
@@ -524,7 +509,7 @@ Error CowData<T>::_copy_to_new_buffer(USize p_min_capacity, USize p_size_from_st
524509 prev_data._ptr = _ptr;
525510 _ptr = nullptr ;
526511
527- const Error error = _alloc (p_min_capacity );
512+ const Error error = _alloc_exact (p_capacity );
528513 if (error) {
529514 // On failure to allocate, recover the old data and return the error.
530515 _ptr = prev_data._ptr ;
@@ -551,7 +536,7 @@ Error CowData<T>::_copy_on_write() {
551536 }
552537
553538 // Fork to become the only reference.
554- return _copy_to_new_buffer (capacity (), size (), 0 , 0 );
539+ return _copy_to_new_buffer_exact (capacity (), size (), 0 , 0 );
555540}
556541
557542template <typename T>
@@ -578,7 +563,7 @@ void CowData<T>::_ref(const CowData &p_from) {
578563
579564template <typename T>
580565CowData<T>::CowData(std::initializer_list<T> p_init) {
581- CRASH_COND (_alloc (p_init.size ()));
566+ CRASH_COND (_alloc_exact (p_init.size ()));
582567
583568 copy_arr_placement (_ptr, p_init.begin (), p_init.size ());
584569 *_get_size () = p_init.size ();
0 commit comments