Skip to content

Commit 056153f

Browse files
authored
Optimize vector::assign for InputIterator-only pair inputs (#113852)
This PR optimizes the input iterator overload of `assign(_InputIterator, _InputIterator)` in `std::vector<_Tp, _Allocator>` by directly assigning to already initialized memory, rather than first destroying existing elements and then constructing new ones. By eliminating unnecessary destruction and construction, the proposed algorithm enhances the performance by up to 2x for trivial element types (e.g., `std::vector<int>`), up to 2.6x for non-trivial element types like `std::vector<std::string>`, and up to 3.4x for more complex non-trivial types (e.g., `std::vector<std::vector<int>>`). ### Google Benchmarks Benchmark tests (`libcxx/test/benchmarks/vector_operations.bench.cpp`) were conducted for the `assign()` implementations before and after this patch. The tests focused on trivial element types like `std::vector<int>`, and non-trivial element types such as `std::vector<std::string>` and `std::vector<std::vector<int>>`. #### Before ``` ------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------------------- BM_AssignInputIterIter/vector_int/1024/1024 1157 ns 1169 ns 608188 BM_AssignInputIterIter<32>/vector_string/1024/1024 14559 ns 14710 ns 47277 BM_AssignInputIterIter<32>/vector_vector_int/1024/1024 26846 ns 27129 ns 25925 ``` #### After ``` ------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------------------- BM_AssignInputIterIter/vector_int/1024/1024 561 ns 566 ns 1242251 BM_AssignInputIterIter<32>/vector_string/1024/1024 5604 ns 5664 ns 128365 BM_AssignInputIterIter<32>/vector_vector_int/1024/1024 7927 ns 8012 ns 88579 ```
1 parent 961c66c commit 056153f

File tree

5 files changed

+56
-7
lines changed

5 files changed

+56
-7
lines changed

libcxx/docs/ReleaseNotes/20.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ Improvements and New Features
6969
- The ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` ABI configuration was added, which allows storing valid bounds
7070
in ``std::array::iterator`` and detecting OOB accesses when the appropriate hardening mode is enabled.
7171

72+
- The input iterator overload of `assign(_InputIterator, _InputIterator)` in `std::vector<_Tp, _Allocator>` has been
73+
optimized, resulting in a performance improvement of up to 2x for trivial element types (e.g., `std::vector<int>`),
74+
and up to 3.4x for non-trivial element types (e.g., `std::vector<std::vector<int>>`).
75+
7276
Deprecations and Removals
7377
-------------------------
7478

libcxx/include/__vector/vector.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,9 +1003,15 @@ template <class _Tp, class _Allocator>
10031003
template <class _Iterator, class _Sentinel>
10041004
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void
10051005
vector<_Tp, _Allocator>::__assign_with_sentinel(_Iterator __first, _Sentinel __last) {
1006-
clear();
1007-
for (; __first != __last; ++__first)
1008-
emplace_back(*__first);
1006+
pointer __cur = __begin_;
1007+
for (; __first != __last && __cur != __end_; ++__first, (void)++__cur)
1008+
*__cur = *__first;
1009+
if (__cur != __end_) {
1010+
__destruct_at_end(__cur);
1011+
} else {
1012+
for (; __first != __last; ++__first)
1013+
emplace_back(*__first);
1014+
}
10091015
}
10101016

10111017
template <class _Tp, class _Allocator>

libcxx/test/benchmarks/ContainerBenchmarks.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
#include <iterator>
1515
#include <utility>
1616

17-
#include "Utilities.h"
1817
#include "benchmark/benchmark.h"
18+
#include "Utilities.h"
19+
#include "test_iterators.h"
1920

2021
namespace ContainerBenchmarks {
2122

@@ -50,6 +51,19 @@ void BM_Assignment(benchmark::State& st, Container) {
5051
}
5152
}
5253

54+
template <std::size_t... sz, typename Container, typename GenInputs>
55+
void BM_AssignInputIterIter(benchmark::State& st, Container c, GenInputs gen) {
56+
auto v = gen(1, sz...);
57+
c.resize(st.range(0), v[0]);
58+
auto in = gen(st.range(1), sz...);
59+
benchmark::DoNotOptimize(&in);
60+
benchmark::DoNotOptimize(&c);
61+
for (auto _ : st) {
62+
c.assign(cpp17_input_iterator(in.begin()), cpp17_input_iterator(in.end()));
63+
benchmark::ClobberMemory();
64+
}
65+
}
66+
5367
template <class Container>
5468
void BM_ConstructSizeValue(benchmark::State& st, Container, typename Container::value_type const& val) {
5569
const auto size = st.range(0);

libcxx/test/benchmarks/GenerateInput.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,29 @@ std::vector<IntT> getRandomIntegerInputs(std::size_t N) {
108108
return inputs;
109109
}
110110

111+
inline std::vector<std::string> getRandomStringInputsWithLength(std::size_t N, std::size_t len) { // N-by-len
112+
std::vector<std::string> inputs;
113+
inputs.reserve(N);
114+
for (std::size_t i = 0; i < N; ++i)
115+
inputs.push_back(getRandomString(len));
116+
return inputs;
117+
}
118+
111119
inline std::vector<std::string> getDuplicateStringInputs(std::size_t N) {
112120
std::vector<std::string> inputs(N, getRandomString(1024));
113121
return inputs;
114122
}
115123

116124
inline std::vector<std::string> getRandomStringInputs(std::size_t N) {
117-
std::vector<std::string> inputs;
125+
return getRandomStringInputsWithLength(N, 1024);
126+
}
127+
128+
template <class IntT>
129+
std::vector<std::vector<IntT>> getRandomIntegerInputsWithLength(std::size_t N, std::size_t len) { // N-by-len
130+
std::vector<std::vector<IntT>> inputs;
118131
inputs.reserve(N);
119132
for (std::size_t i = 0; i < N; ++i)
120-
inputs.push_back(getRandomString(1024));
133+
inputs.push_back(getRandomIntegerInputs<IntT>(len));
121134
return inputs;
122135
}
123136

libcxx/test/benchmarks/vector_operations.bench.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <vector>
1919

2020
#include "benchmark/benchmark.h"
21-
2221
#include "ContainerBenchmarks.h"
2322
#include "GenerateInput.h"
2423

@@ -79,4 +78,17 @@ BENCHMARK(bm_grow<std::string>);
7978
BENCHMARK(bm_grow<std::unique_ptr<int>>);
8079
BENCHMARK(bm_grow<std::deque<int>>);
8180

81+
BENCHMARK_CAPTURE(BM_AssignInputIterIter, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
82+
->Args({TestNumInputs, TestNumInputs});
83+
84+
BENCHMARK_CAPTURE(
85+
BM_AssignInputIterIter<32>, vector_string, std::vector<std::string>{}, getRandomStringInputsWithLength)
86+
->Args({TestNumInputs, TestNumInputs});
87+
88+
BENCHMARK_CAPTURE(BM_AssignInputIterIter<100>,
89+
vector_vector_int,
90+
std::vector<std::vector<int>>{},
91+
getRandomIntegerInputsWithLength<int>)
92+
->Args({TestNumInputs, TestNumInputs});
93+
8294
BENCHMARK_MAIN();

0 commit comments

Comments
 (0)