Skip to content

Commit 6bfcfd1

Browse files
committed
Merge pull request #106039 from Ivorforce/cowdata-reserve-exact
Add `reserve_exact` to `CowData`, and change growth factor to 1.5x
2 parents 45dfd4d + c993db9 commit 6bfcfd1

File tree

4 files changed

+78
-82
lines changed

4 files changed

+78
-82
lines changed

core/io/file_access.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
569569
return data;
570570
}
571571

572+
data.reserve_exact(p_length);
572573
Error err = data.resize(p_length);
573574
ERR_FAIL_COND_V_MSG(err != OK, data, vformat("Can't resize data to %d elements.", p_length));
574575

@@ -863,6 +864,7 @@ Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_err
863864
ERR_FAIL_V_MSG(Vector<uint8_t>(), vformat("Can't open file from path '%s'.", String(p_path)));
864865
}
865866
Vector<uint8_t> data;
867+
data.reserve_exact(f->get_length());
866868
data.resize(f->get_length());
867869
f->get_buffer(data.ptrw(), data.size());
868870
return data;

core/templates/cowdata.h

Lines changed: 67 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -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

375369
template <typename T>
370+
template <bool p_exact>
376371
Error 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

468463
template <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

489481
template <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

515500
template <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

557542
template <typename T>
@@ -578,7 +563,7 @@ void CowData<T>::_ref(const CowData &p_from) {
578563

579564
template <typename T>
580565
CowData<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();

core/templates/local_vector.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,11 @@ class LocalVector {
166166
if (tight) {
167167
capacity = p_size;
168168
} else {
169+
// Try 1.5x the current capacity.
170+
// This ratio was chosen because it is close to the ideal growth rate of the golden ratio.
171+
// See https://archive.ph/Z2R8w for details.
169172
capacity = MAX((U)2, capacity + ((1 + capacity) >> 1));
173+
// If 1.5x growth isn't enough, just use the needed size exactly.
170174
if (p_size > capacity) {
171175
capacity = p_size;
172176
}

core/templates/vector.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ class Vector {
126126
return _cowdata.reserve(p_size);
127127
}
128128

129+
Error reserve_exact(Size p_size) {
130+
ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
131+
return _cowdata.reserve_exact(p_size);
132+
}
133+
129134
_FORCE_INLINE_ const T &operator[](Size p_index) const { return _cowdata.get(p_index); }
130135
// Must take a copy instead of a reference (see GH-31736).
131136
Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }

0 commit comments

Comments
 (0)