Skip to content

Commit 9ab1190

Browse files
kuharanonymouspc
authored andcommitted
[ADT] Only call reserve on empty containers in append_values (llvm#172109)
Calling reserve in a loop can prevent amortized constant time appends when the container doesn't round up the capacity.
1 parent 88a8ef9 commit 9ab1190

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

llvm/include/llvm/ADT/STLExtras.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,17 @@ void append_range(Container &C, Range &&R) {
21522152
/// Appends all `Values` to container `C`.
21532153
template <typename Container, typename... Args>
21542154
void append_values(Container &C, Args &&...Values) {
2155-
C.reserve(range_size(C) + sizeof...(Args));
2155+
if (size_t InitialSize = range_size(C); InitialSize == 0) {
2156+
// Only reserve if the container is empty. Reserving on a non-empty
2157+
// container may interfere with the exponential growth strategy, if the
2158+
// container does not round up the capacity. Consider `append_values` called
2159+
// repeatedly in a loop: each call would reserve exactly `size + N`, causing
2160+
// the capacity to grow linearly (e.g., 100 -> 105 -> 110 -> ...) instead of
2161+
// exponentially (e.g., 100 -> 200 -> ...). Linear growth turns the
2162+
// amortized O(1) append into O(n) because every few insertions trigger a
2163+
// reallocation and copy of all elements.
2164+
C.reserve(InitialSize + sizeof...(Args));
2165+
}
21562166
// Append all values one by one.
21572167
((void)C.insert(C.end(), std::forward<Args>(Values)), ...);
21582168
}

llvm/unittests/ADT/STLExtrasTest.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,41 @@ TEST(STLExtrasTest, AppendValues) {
701701
EXPECT_THAT(Set, UnorderedElementsAre(1, 2, 3));
702702
}
703703

704+
TEST(STLExtrasTest, AppendValuesReserve) {
705+
// A vector wrapper that tracks reserve() calls.
706+
struct TrackedVector : std::vector<int> {
707+
using std::vector<int>::vector;
708+
size_t LastReservedSize = 0;
709+
unsigned ReserveCallCount = 0;
710+
711+
void reserve(size_t N) {
712+
LastReservedSize = N;
713+
++ReserveCallCount;
714+
std::vector<int>::reserve(N);
715+
}
716+
};
717+
718+
// When empty, reserve should be called.
719+
TrackedVector Empty;
720+
append_values(Empty, 1, 2, 3);
721+
EXPECT_EQ(Empty.ReserveCallCount, 1u);
722+
EXPECT_EQ(Empty.LastReservedSize, 3u);
723+
EXPECT_THAT(Empty, ElementsAre(1, 2, 3));
724+
725+
// Appending more values to a now non-empty container should still not
726+
// reserve.
727+
append_values(Empty, 4, 5);
728+
EXPECT_EQ(Empty.ReserveCallCount, 1u);
729+
EXPECT_THAT(Empty, ElementsAre(1, 2, 3, 4, 5));
730+
731+
// When non-empty, reserve should NOT be called to avoid preventing
732+
// exponential growth.
733+
TrackedVector NonEmpty = {1, 2};
734+
append_values(NonEmpty, 3, 4);
735+
EXPECT_EQ(NonEmpty.ReserveCallCount, 0u);
736+
EXPECT_THAT(NonEmpty, ElementsAre(1, 2, 3, 4));
737+
}
738+
704739
TEST(STLExtrasTest, ADLTest) {
705740
some_namespace::some_struct s{{1, 2, 3, 4, 5}, ""};
706741
some_namespace::some_struct s2{{2, 4, 6, 8, 10}, ""};

0 commit comments

Comments
 (0)