From 389f29f0e7cd9da96d23df8f1d054c2533602d1f Mon Sep 17 00:00:00 2001 From: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:26:37 -0800 Subject: [PATCH] Performance improvement for fixed_vector AZStd::fixed_vector had all its functions marked with constexpr, but this requires all member variables to be fully initialized. This meant that the internal array used to store elements always has to be fully initialized. This was done for trivial classes but not for non-trivial classes. As a result trivial classes always did a memset (or more optimized versions for smaller buffers) while the non-trivial version couldn't actually be stored in a constexpr variable. Since AZStd::fixed_vector is meant to be dynamic the choice was made to remove the constexpr from all non-static member functions in favor of avoiding the overhead of memset, which profiling showed was a considerable overhead depending on the reserved size. If a truly constexpr array is needed than AZStd::array is a better choice as that's designed to not by dynamic. Signed-off-by: AMZN-koppersr <82230785+AMZN-koppersr@users.noreply.github.com> --- .../AzCore/std/containers/fixed_vector.h | 152 +++++++++--------- .../AzCore/Tests/AZStd/VectorAndArray.cpp | 42 ++--- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h b/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h index 7c0cca0308..5a54c2a589 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h +++ b/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h @@ -130,10 +130,11 @@ namespace AZStd::Internal //! Constructors - constexpr fixed_trivial_storage() = default; + fixed_trivial_storage() = default; template >> - constexpr fixed_trivial_storage(AZStd::initializer_list ilist) noexcept + fixed_trivial_storage(AZStd::initializer_list ilist) noexcept + : m_size(aznumeric_caster(ilist.size())) { AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); size_t index{}; @@ -141,20 +142,19 @@ namespace AZStd::Internal { m_data[index++] = element; } - resize_no_construct(ilist.size()); } - constexpr pointer data() noexcept + pointer data() noexcept { return m_data; } - constexpr const_pointer data() const noexcept + const_pointer data() const noexcept { return m_data; } //! Number of elements currently stored. - constexpr size_type size() const noexcept + size_type size() const noexcept { return m_size; } @@ -164,12 +164,12 @@ namespace AZStd::Internal return Capacity; } //! Is the storage empty? - constexpr bool empty() const noexcept + bool empty() const noexcept { return size() == 0; } //! Is the storage full? - constexpr bool full() const noexcept + bool full() const noexcept { return size() == capacity(); } @@ -186,7 +186,7 @@ namespace AZStd::Internal //! Increases size of the storage by one. //! Always fails for empty storage. template >> - constexpr reference emplace_back(Args&&... args) noexcept + reference emplace_back(Args&&... args) noexcept { AZSTD_CONTAINER_ASSERT(!full(), "emplace_back cannot be invoked on full storage"); reference new_element = *(data() + size()); @@ -196,7 +196,7 @@ namespace AZStd::Internal } //! Removes the last element of the storage. //! Precondition: size is not empty - constexpr void pop_back() noexcept + void pop_back() noexcept { AZSTD_CONTAINER_ASSERT(!empty(), "pop_back cannot be invoked on empty storage"); resize_no_construct(size() - 1); @@ -205,7 +205,7 @@ namespace AZStd::Internal //! removing elements (unsafe). //! //! Updates the size of the container while checking that the new size is less than capacity - constexpr void resize_no_construct(size_t new_size) noexcept + void resize_no_construct(size_t new_size) noexcept { AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); m_size = aznumeric_cast(new_size); @@ -215,19 +215,19 @@ namespace AZStd::Internal //! This does not modify the size of the storage //! This is a no-op for trivial types template >> - constexpr void unsafe_destroy(InputIt, InputIt) noexcept + void unsafe_destroy(InputIt, InputIt) noexcept { } //! Destructs all elements of the storage. //! This does not modify the size of the storage //! This is a no-op for trivial types - constexpr void unsafe_destroy_all() noexcept + void unsafe_destroy_all() noexcept { } private: - T m_data[Capacity]{}; + T m_data[Capacity]; size_type m_size{}; }; @@ -245,7 +245,7 @@ namespace AZStd::Internal using reference = T&; using const_reference = const T&; - constexpr fixed_non_trivial_storage() = default; + fixed_non_trivial_storage() = default; ~fixed_non_trivial_storage() noexcept { @@ -253,7 +253,7 @@ namespace AZStd::Internal } template >> - constexpr fixed_non_trivial_storage(AZStd::initializer_list ilist) noexcept(noexcept(emplace_back(AZStd::declval()))) + fixed_non_trivial_storage(AZStd::initializer_list ilist) noexcept(noexcept(emplace_back(AZStd::declval()))) { AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); for (const U& element : ilist) @@ -272,7 +272,7 @@ namespace AZStd::Internal } //! Number of elements currently stored. - constexpr size_type size() const noexcept + size_type size() const noexcept { return m_size; } @@ -282,12 +282,12 @@ namespace AZStd::Internal return Capacity; } //! Is the storage empty? - constexpr bool empty() const noexcept + bool empty() const noexcept { return size() == 0; } //! Is the storage full? - constexpr bool full() const noexcept + bool full() const noexcept { return size() == capacity(); } @@ -325,7 +325,7 @@ namespace AZStd::Internal //! removing elements (unsafe). //! //! Updates the size of the container while checking that the new size is less than capacity - constexpr void resize_no_construct(size_t new_size) noexcept + void resize_no_construct(size_t new_size) noexcept { AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); m_size = aznumeric_cast(new_size); @@ -402,23 +402,23 @@ namespace AZStd ////////////////////////////////////////////////////////////////////////// // 23.2.4.1 construct/copy/destroy - constexpr fixed_vector() = default; + fixed_vector() = default; - constexpr explicit fixed_vector(size_type numElements, const_reference value = value_type()) + explicit fixed_vector(size_type numElements, const_reference value = value_type()) { resize_no_construct(numElements); AZStd::uninitialized_fill_n(data(), numElements, value); } template >> - constexpr fixed_vector(InputIt first, InputIt last) + fixed_vector(InputIt first, InputIt last) { resize_no_construct(AZStd::distance(first, last)); AZStd::uninitialized_copy(first, last, data()); } - constexpr fixed_vector(const fixed_vector& rhs) + fixed_vector(const fixed_vector& rhs) { resize_no_construct(rhs.size()); AZStd::uninitialized_copy(rhs.data(), rhs.data() + rhs.size(), data()); @@ -428,7 +428,7 @@ namespace AZStd // It performs an AZStd::move on each of the fixed_vector elements instead // of swapping pointers to the allocted memory address // as it is unable to perform that operations due to the storage being baked into the container - constexpr fixed_vector(fixed_vector&& rhs) + fixed_vector(fixed_vector&& rhs) { resize_no_construct(rhs.size()); AZStd::uninitialized_move(rhs.data(), rhs.data() + rhs.size(), data()); @@ -440,7 +440,7 @@ namespace AZStd // into a fixed_vector given that the type in question isn't the same type as this fixed_vector type template && !AZStd::is_convertible_v>> - constexpr fixed_vector(VectorContainer&& rhs) + fixed_vector(VectorContainer&& rhs) { constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v || AZStd::is_const_v; @@ -459,12 +459,12 @@ namespace AZStd } } - constexpr fixed_vector(AZStd::initializer_list ilist) + fixed_vector(AZStd::initializer_list ilist) : base_type(ilist) { } - constexpr fixed_vector& operator=(const fixed_vector& rhs) + fixed_vector& operator=(const fixed_vector& rhs) { if (this == &rhs) { @@ -475,7 +475,7 @@ namespace AZStd return assign_helper(rhs); } - constexpr fixed_vector& operator=(fixed_vector&& rhs) + fixed_vector& operator=(fixed_vector&& rhs) { if (this == &rhs) { @@ -487,23 +487,23 @@ namespace AZStd } template - constexpr AZStd::enable_if_t, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs) + AZStd::enable_if_t, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs) { return assign_helper(AZStd::forward(rhs)); } - constexpr iterator begin() { return iterator(data()); } - constexpr const_iterator begin() const { return const_iterator(data()); } - constexpr const_iterator cbegin() const { return const_iterator(data()); } - constexpr iterator end() { return iterator(data() + size()); } - constexpr const_iterator end() const { return const_iterator(data() + size()); } - constexpr const_iterator cend() const { return const_iterator(data() + size()); } - constexpr reverse_iterator rbegin() { return reverse_iterator(end()); } - constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } - constexpr reverse_iterator rend() { return reverse_iterator(begin()); } - constexpr const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - constexpr const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } + iterator begin() { return iterator(data()); } + const_iterator begin() const { return const_iterator(data()); } + const_iterator cbegin() const { return const_iterator(data()); } + iterator end() { return iterator(data() + size()); } + const_iterator end() const { return const_iterator(data() + size()); } + const_iterator cend() const { return const_iterator(data() + size()); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } // bring in fixed_vector_storage functions into scope using base_type::data; @@ -514,7 +514,7 @@ namespace AZStd // extension method using base_type::resize_no_construct; - constexpr size_type size() const noexcept + size_type size() const noexcept { return base_type::size(); } @@ -527,12 +527,12 @@ namespace AZStd return base_type::max_size(); } - constexpr void resize(size_type newSize) + void resize(size_type newSize) { return resize(newSize, value_type{}); } - constexpr void resize(size_type newSize, const_reference value) + void resize(size_type newSize, const_reference value) { size_type dataSize = size(); if (dataSize < newSize) @@ -547,7 +547,7 @@ namespace AZStd // Removes unused capacity - For fixed_vector this only asserts // that the supplied capacity is not longer than the fixed_vector capacity - constexpr void reserve(size_type newCapacity) + void reserve(size_type newCapacity) { // No-op - Implemented to provide consistent std::vector AZSTD_CONTAINER_ASSERT(newCapacity <= capacity(), @@ -556,79 +556,79 @@ namespace AZStd } // Removes unused capacity - For fixed_vector this does nothing - constexpr void shrink_to_fit() + void shrink_to_fit() { // No-op - Implemented to provide consistent std::vector } - constexpr reference at(size_type position) + reference at(size_type position) { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr const_reference at(size_type position) const + const_reference at(size_type position) const { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr reference operator[](size_type position) + reference operator[](size_type position) { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr const_reference operator[](size_type position) const + const_reference operator[](size_type position) const { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr reference front() + reference front() { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); return *data(); } - constexpr const_reference front() const + const_reference front() const { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); return *data(); } - constexpr reference back() + reference back() { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); return *(data() + size() - 1); } - constexpr const_reference back() const + const_reference back() const { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); return *(data() + size() - 1); } - constexpr void push_back(const_reference value) + void push_back(const_reference value) { emplace_back(value); } - constexpr void assign(size_type numElements, const_reference value) + void assign(size_type numElements, const_reference value) { clear(); insert(end(), numElements, value); } template >> - constexpr void assign(InputIt first, InputIt last) + void assign(InputIt first, InputIt last) { clear(); insert(end(), first, last); } - constexpr void assign(AZStd::initializer_list ilist) + void assign(AZStd::initializer_list ilist) { assign(ilist.begin(), ilist.end()); } template >> - constexpr iterator emplace(const_iterator insertPos, Args&&... args) + iterator emplace(const_iterator insertPos, Args&&... args) { AZSTD_CONTAINER_ASSERT(!full(), "Cannot emplace on a full fixed_vector"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); @@ -645,18 +645,18 @@ namespace AZStd AZStd::construct_at(insertPosPtr, AZStd::forward(args)...); return iterator(insertPosPtr); } - constexpr iterator insert(const_iterator insertPos, const_reference value) + iterator insert(const_iterator insertPos, const_reference value) { AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); return emplace(insertPos, value); } - constexpr iterator insert(const_iterator insertPos, value_type&& value) + iterator insert(const_iterator insertPos, value_type&& value) { AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); return emplace(insertPos, AZStd::move(value)); } - constexpr void insert(const_iterator insertPos, size_type numElements, const_reference value) + void insert(const_iterator insertPos, size_type numElements, const_reference value) { if (numElements == 0) { @@ -708,24 +708,24 @@ namespace AZStd } template>> - constexpr void insert(const_iterator insertPos, InputIt first, InputIt last) + void insert(const_iterator insertPos, InputIt first, InputIt last) { // specialize for iterator categories. AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); insert_iter(insertPos, first, last, typename iterator_traits::iterator_category()); }; - constexpr void insert(const_iterator insertPos, AZStd::initializer_list ilist) + void insert(const_iterator insertPos, AZStd::initializer_list ilist) { insert(insertPos, ilist.begin(), ilist.end()); } - constexpr iterator erase(const_iterator elementIter) + iterator erase(const_iterator elementIter) { return erase(elementIter, elementIter + 1); } - constexpr iterator erase(const_iterator first, const_iterator last) + iterator erase(const_iterator first, const_iterator last) { AZSTD_CONTAINER_ASSERT(first >= cbegin() && last <= cend(), "erase iterator must be inside the range of fixed_vector container"); iterator dataStart = begin(); @@ -741,12 +741,12 @@ namespace AZStd return dataStart + offset; } - constexpr void clear() + void clear() { base_type::unsafe_destroy_all(); resize_no_construct(0); } - constexpr void swap(fixed_vector& rhs) + void swap(fixed_vector& rhs) { // Fixed containers cannot swap pointers, they need to do full copies. // The strategy is to extend the smaller fixed_vector to be the size @@ -776,12 +776,12 @@ namespace AZStd } // Validate container status. - constexpr bool validate() const + bool validate() const { return size() <= max_size(); } // Validate iterator. - constexpr int validate_iterator(const_iterator iter) const + int validate_iterator(const_iterator iter) const { const_pointer start = data(); const_pointer end = data() + size(); @@ -799,19 +799,19 @@ namespace AZStd } // pushes back an empty without a provided instance. - constexpr void push_back() + void push_back() { emplace_back(); } - constexpr void leak_and_reset() + void leak_and_reset() { resize_no_construct(0); } private: template - constexpr fixed_vector& assign_helper(VectorContainer&& rhs) + fixed_vector& assign_helper(VectorContainer&& rhs) { constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v || AZStd::is_const_v; @@ -872,7 +872,7 @@ namespace AZStd } template - constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&) + void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&) { size_type numElements = AZStd::distance(first, last); if (numElements == 0) @@ -923,7 +923,7 @@ namespace AZStd } template - constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&) + void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&) { iterator dataStart = data(); size_type offset = AZStd::distance(dataStart, insertPos); diff --git a/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp b/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp index 47c3630091..b3eab244c4 100644 --- a/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp @@ -753,7 +753,7 @@ namespace UnitTest TEST_F(Arrays, FixedVectorCanCopyAndMoveWithDifferentCapacity) { - constexpr AZStd::fixed_vector sourceVector{ 1,2,3,4,5 }; + AZStd::fixed_vector sourceVector{ 1,2,3,4,5 }; AZStd::fixed_vector copyConstructVector{ sourceVector }; EXPECT_EQ(sourceVector, copyConstructVector); @@ -768,32 +768,32 @@ namespace UnitTest AZStd::fixed_vector moveAssignVector = AZStd::move(moveConstructVector); - constexpr AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 }; + AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 }; EXPECT_EQ(expectedVector, moveAssignVector); } TEST_F(Arrays, FixedVectorComparisonOperatorsSucceedAsExpected) { - constexpr AZStd::fixed_vector testVector{ 1,2,3,4,5 }; - constexpr AZStd::fixed_vector equalVector{ 1,2,3,4,5 }; - constexpr AZStd::fixed_vector notEqualVectorDifferentSize{ 1,2,3,4,5,6 }; - constexpr AZStd::fixed_vector lessVector{ 1,2,3,4,4 }; - constexpr AZStd::fixed_vector greaterVectorDifferentSize{ 1,2,3,4,5, 1 }; + AZStd::fixed_vector testVector{ 1,2,3,4,5 }; + AZStd::fixed_vector equalVector{ 1,2,3,4,5 }; + AZStd::fixed_vector notEqualVectorDifferentSize{ 1,2,3,4,5,6 }; + AZStd::fixed_vector lessVector{ 1,2,3,4,4 }; + AZStd::fixed_vector greaterVectorDifferentSize{ 1,2,3,4,5, 1 }; - static_assert(testVector == equalVector); - static_assert(testVector != notEqualVectorDifferentSize); - static_assert(testVector != lessVector); - static_assert(lessVector < testVector); - static_assert(lessVector < greaterVectorDifferentSize); - static_assert(lessVector <= lessVector); - static_assert(lessVector <= testVector); - static_assert(lessVector <= greaterVectorDifferentSize); - static_assert(testVector > lessVector); - static_assert(testVector > lessVector); - static_assert(notEqualVectorDifferentSize > testVector); - static_assert(testVector >= testVector); - static_assert(testVector >= lessVector); - static_assert(greaterVectorDifferentSize > lessVector); + EXPECT_EQ(testVector, equalVector); + EXPECT_NE(testVector, notEqualVectorDifferentSize); + EXPECT_NE(testVector, lessVector); + EXPECT_LT(lessVector, testVector); + EXPECT_LT(lessVector, greaterVectorDifferentSize); + EXPECT_LE(lessVector, lessVector); + EXPECT_LE(lessVector, testVector); + EXPECT_LE(lessVector, greaterVectorDifferentSize); + EXPECT_GT(testVector, lessVector); + EXPECT_GT(testVector, lessVector); + EXPECT_GT(notEqualVectorDifferentSize, testVector); + EXPECT_GE(testVector, testVector); + EXPECT_GE(testVector, lessVector); + EXPECT_GT(greaterVectorDifferentSize, lessVector); } TEST_F(Arrays, VectorSwap)