diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h index 3c18aae1b519c..409cd7b60fa00 100644 --- a/llvm/include/llvm/ADT/STLExtras.h +++ b/llvm/include/llvm/ADT/STLExtras.h @@ -2152,7 +2152,17 @@ void append_range(Container &C, Range &&R) { /// Appends all `Values` to container `C`. template void append_values(Container &C, Args &&...Values) { - C.reserve(range_size(C) + sizeof...(Args)); + if (size_t InitialSize = range_size(C); InitialSize == 0) { + // Only reserve if the container is empty. Reserving on a non-empty + // container may interfere with the exponential growth strategy, if the + // container does not round up the capacity. Consider `append_values` called + // repeatedly in a loop: each call would reserve exactly `size + N`, causing + // the capacity to grow linearly (e.g., 100 -> 105 -> 110 -> ...) instead of + // exponentially (e.g., 100 -> 200 -> ...). Linear growth turns the + // amortized O(1) append into O(n) because every few insertions trigger a + // reallocation and copy of all elements. + C.reserve(InitialSize + sizeof...(Args)); + } // Append all values one by one. ((void)C.insert(C.end(), std::forward(Values)), ...); } diff --git a/llvm/unittests/ADT/STLExtrasTest.cpp b/llvm/unittests/ADT/STLExtrasTest.cpp index e356f6b540568..dbf439b8d63a0 100644 --- a/llvm/unittests/ADT/STLExtrasTest.cpp +++ b/llvm/unittests/ADT/STLExtrasTest.cpp @@ -701,6 +701,41 @@ TEST(STLExtrasTest, AppendValues) { EXPECT_THAT(Set, UnorderedElementsAre(1, 2, 3)); } +TEST(STLExtrasTest, AppendValuesReserve) { + // A vector wrapper that tracks reserve() calls. + struct TrackedVector : std::vector { + using std::vector::vector; + size_t LastReservedSize = 0; + unsigned ReserveCallCount = 0; + + void reserve(size_t N) { + LastReservedSize = N; + ++ReserveCallCount; + std::vector::reserve(N); + } + }; + + // When empty, reserve should be called. + TrackedVector Empty; + append_values(Empty, 1, 2, 3); + EXPECT_EQ(Empty.ReserveCallCount, 1u); + EXPECT_EQ(Empty.LastReservedSize, 3u); + EXPECT_THAT(Empty, ElementsAre(1, 2, 3)); + + // Appending more values to a now non-empty container should still not + // reserve. + append_values(Empty, 4, 5); + EXPECT_EQ(Empty.ReserveCallCount, 1u); + EXPECT_THAT(Empty, ElementsAre(1, 2, 3, 4, 5)); + + // When non-empty, reserve should NOT be called to avoid preventing + // exponential growth. + TrackedVector NonEmpty = {1, 2}; + append_values(NonEmpty, 3, 4); + EXPECT_EQ(NonEmpty.ReserveCallCount, 0u); + EXPECT_THAT(NonEmpty, ElementsAre(1, 2, 3, 4)); +} + TEST(STLExtrasTest, ADLTest) { some_namespace::some_struct s{{1, 2, 3, 4, 5}, ""}; some_namespace::some_struct s2{{2, 4, 6, 8, 10}, ""};