Skip to content

Commit 3a3517c

Browse files
authored
[libc++] Improve the tests for vector::erase (#116265)
In particular, test everything with both a normal and a min_allocator, add tests for a few corner cases and add tests with types that are trivially relocatable. Also add tests that count the number of assignments performed by vector::erase, since that is mandated by the Standard. This patch is a preparation for optimizing vector::erase.
1 parent 2de7881 commit 3a3517c

File tree

6 files changed

+370
-241
lines changed

6 files changed

+370
-241
lines changed

libcxx/test/benchmarks/ContainerBenchmarks.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#define BENCHMARK_CONTAINER_BENCHMARKS_H
1212

1313
#include <cassert>
14+
#include <iterator>
15+
#include <utility>
1416

1517
#include "Utilities.h"
1618
#include "benchmark/benchmark.h"
@@ -149,6 +151,34 @@ void BM_EmplaceDuplicate(benchmark::State& st, Container c, GenInputs gen) {
149151
}
150152
}
151153

154+
template <class Container, class GenInputs>
155+
void BM_erase_iter_in_middle(benchmark::State& st, Container, GenInputs gen) {
156+
auto in = gen(st.range(0));
157+
Container c(in.begin(), in.end());
158+
assert(c.size() > 2);
159+
for (auto _ : st) {
160+
auto mid = std::next(c.begin(), c.size() / 2);
161+
auto tmp = *mid;
162+
auto result = c.erase(mid); // erase an element in the middle
163+
benchmark::DoNotOptimize(result);
164+
c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container
165+
}
166+
}
167+
168+
template <class Container, class GenInputs>
169+
void BM_erase_iter_at_start(benchmark::State& st, Container, GenInputs gen) {
170+
auto in = gen(st.range(0));
171+
Container c(in.begin(), in.end());
172+
assert(c.size() > 2);
173+
for (auto _ : st) {
174+
auto it = c.begin();
175+
auto tmp = *it;
176+
auto result = c.erase(it); // erase the first element
177+
benchmark::DoNotOptimize(result);
178+
c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container
179+
}
180+
}
181+
152182
template <class Container, class GenInputs>
153183
void BM_Find(benchmark::State& st, Container c, GenInputs gen) {
154184
auto in = gen(st.range(0));

libcxx/test/benchmarks/deque.bench.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
1010

1111
#include <deque>
12+
#include <string>
1213

1314
#include "benchmark/benchmark.h"
1415

@@ -41,4 +42,14 @@ BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_size_t, std::deque<size_t>{}, get
4142
BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_string, std::deque<std::string>{}, getRandomStringInputs)
4243
->Arg(TestNumInputs);
4344

45+
BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_int, std::deque<int>{}, getRandomIntegerInputs<int>)
46+
->Range(TestNumInputs, TestNumInputs * 10);
47+
BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_string, std::deque<std::string>{}, getRandomStringInputs)
48+
->Range(TestNumInputs, TestNumInputs * 10);
49+
50+
BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_int, std::deque<int>{}, getRandomIntegerInputs<int>)
51+
->Range(TestNumInputs, TestNumInputs * 10);
52+
BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_string, std::deque<std::string>{}, getRandomStringInputs)
53+
->Range(TestNumInputs, TestNumInputs * 10);
54+
4455
BENCHMARK_MAIN();

libcxx/test/benchmarks/vector_operations.bench.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_string, std::vector<std::string>
5454

5555
BENCHMARK_CAPTURE(BM_Pushback_no_grow, vector_int, std::vector<int>{})->Arg(TestNumInputs);
5656

57+
BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
58+
->Range(TestNumInputs, TestNumInputs * 10);
59+
BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_string, std::vector<std::string>{}, getRandomStringInputs)
60+
->Range(TestNumInputs, TestNumInputs * 10);
61+
62+
BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
63+
->Range(TestNumInputs, TestNumInputs * 10);
64+
BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_string, std::vector<std::string>{}, getRandomStringInputs)
65+
->Range(TestNumInputs, TestNumInputs * 10);
66+
5767
template <class T>
5868
void bm_grow(benchmark::State& state) {
5969
for (auto _ : state) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H
10+
#define TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H
11+
12+
#include "test_macros.h"
13+
14+
#include <type_traits> // for __libcpp_is_trivially_relocatable
15+
16+
#ifndef TEST_HAS_NO_EXCEPTIONS
17+
struct Throws {
18+
Throws() : v_(0) {}
19+
Throws(int v) : v_(v) {}
20+
Throws(const Throws& rhs) : v_(rhs.v_) {
21+
if (sThrows)
22+
throw 1;
23+
}
24+
Throws(Throws&& rhs) : v_(rhs.v_) {
25+
if (sThrows)
26+
throw 1;
27+
}
28+
Throws& operator=(const Throws& rhs) {
29+
v_ = rhs.v_;
30+
return *this;
31+
}
32+
Throws& operator=(Throws&& rhs) {
33+
v_ = rhs.v_;
34+
return *this;
35+
}
36+
int v_;
37+
static bool sThrows;
38+
};
39+
40+
bool Throws::sThrows = false;
41+
#endif
42+
43+
struct Tracker {
44+
int copy_assignments = 0;
45+
int move_assignments = 0;
46+
};
47+
48+
struct TrackedAssignment {
49+
Tracker* tracker_;
50+
TEST_CONSTEXPR_CXX14 explicit TrackedAssignment(Tracker* tracker) : tracker_(tracker) {}
51+
52+
TrackedAssignment(TrackedAssignment const&) = default;
53+
TrackedAssignment(TrackedAssignment&&) = default;
54+
55+
TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) {
56+
tracker_->copy_assignments++;
57+
return *this;
58+
}
59+
TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) {
60+
tracker_->move_assignments++;
61+
return *this;
62+
}
63+
};
64+
65+
struct NonTriviallyRelocatable {
66+
int value_;
67+
TEST_CONSTEXPR NonTriviallyRelocatable() : value_(0) {}
68+
TEST_CONSTEXPR explicit NonTriviallyRelocatable(int v) : value_(v) {}
69+
TEST_CONSTEXPR NonTriviallyRelocatable(NonTriviallyRelocatable const& other) : value_(other.value_) {}
70+
TEST_CONSTEXPR NonTriviallyRelocatable(NonTriviallyRelocatable&& other) : value_(other.value_) {}
71+
TEST_CONSTEXPR_CXX14 NonTriviallyRelocatable& operator=(NonTriviallyRelocatable const& other) {
72+
value_ = other.value_;
73+
return *this;
74+
}
75+
TEST_CONSTEXPR_CXX14 NonTriviallyRelocatable& operator=(NonTriviallyRelocatable&& other) {
76+
value_ = other.value_;
77+
return *this;
78+
}
79+
80+
TEST_CONSTEXPR_CXX14 friend bool operator==(NonTriviallyRelocatable const& a, NonTriviallyRelocatable const& b) {
81+
return a.value_ == b.value_;
82+
}
83+
};
84+
LIBCPP_STATIC_ASSERT(!std::__libcpp_is_trivially_relocatable<NonTriviallyRelocatable>::value, "");
85+
86+
#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H

libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp

Lines changed: 85 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -11,135 +11,79 @@
1111
// iterator erase(const_iterator position);
1212

1313
#include <vector>
14-
#include <iterator>
1514
#include <cassert>
15+
#include <memory>
1616

1717
#include "asan_testing.h"
18+
#include "common.h"
1819
#include "min_allocator.h"
1920
#include "MoveOnly.h"
2021
#include "test_macros.h"
2122

22-
#ifndef TEST_HAS_NO_EXCEPTIONS
23-
struct Throws {
24-
Throws() : v_(0) {}
25-
Throws(int v) : v_(v) {}
26-
Throws(const Throws& rhs) : v_(rhs.v_) {
27-
if (sThrows)
28-
throw 1;
29-
}
30-
Throws(Throws&& rhs) : v_(rhs.v_) {
31-
if (sThrows)
32-
throw 1;
33-
}
34-
Throws& operator=(const Throws& rhs) {
35-
v_ = rhs.v_;
36-
return *this;
37-
}
38-
Throws& operator=(Throws&& rhs) {
39-
v_ = rhs.v_;
40-
return *this;
41-
}
42-
int v_;
43-
static bool sThrows;
44-
};
45-
46-
bool Throws::sThrows = false;
47-
#endif
48-
49-
TEST_CONSTEXPR_CXX20 bool tests() {
50-
{
51-
int a1[] = {1, 2, 3, 4, 5};
52-
std::vector<int> l1(a1, a1 + 5);
53-
l1.erase(l1.begin());
54-
assert(is_contiguous_container_asan_correct(l1));
55-
assert(l1 == std::vector<int>(a1 + 1, a1 + 5));
56-
}
23+
template <template <class> class Allocator, class T>
24+
TEST_CONSTEXPR_CXX20 void tests() {
5725
{
58-
int a1[] = {1, 2, 3, 4, 5};
59-
int e1[] = {1, 3, 4, 5};
60-
std::vector<int> l1(a1, a1 + 5);
61-
l1.erase(l1.begin() + 1);
62-
assert(is_contiguous_container_asan_correct(l1));
63-
assert(l1 == std::vector<int>(e1, e1 + 4));
64-
}
65-
{
66-
int a1[] = {1, 2, 3};
67-
std::vector<int> l1(a1, a1 + 3);
68-
std::vector<int>::const_iterator i = l1.begin();
69-
assert(is_contiguous_container_asan_correct(l1));
70-
++i;
71-
std::vector<int>::iterator j = l1.erase(i);
72-
assert(l1.size() == 2);
73-
assert(std::distance(l1.begin(), l1.end()) == 2);
74-
assert(*j == 3);
75-
assert(*l1.begin() == 1);
76-
assert(*std::next(l1.begin()) == 3);
77-
assert(is_contiguous_container_asan_correct(l1));
78-
j = l1.erase(j);
79-
assert(j == l1.end());
80-
assert(l1.size() == 1);
81-
assert(std::distance(l1.begin(), l1.end()) == 1);
82-
assert(*l1.begin() == 1);
83-
assert(is_contiguous_container_asan_correct(l1));
84-
j = l1.erase(l1.begin());
85-
assert(j == l1.end());
86-
assert(l1.size() == 0);
87-
assert(std::distance(l1.begin(), l1.end()) == 0);
88-
assert(is_contiguous_container_asan_correct(l1));
89-
}
26+
T arr[] = {T(1), T(2), T(3), T(4), T(5)};
27+
using Vector = std::vector<T, Allocator<T> >;
28+
using Iterator = typename Vector::iterator;
9029

91-
// Make sure vector::erase works with move-only types
92-
// When non-trivial
93-
{
94-
std::vector<MoveOnly> v;
95-
v.emplace_back(1);
96-
v.emplace_back(2);
97-
v.emplace_back(3);
98-
v.erase(v.begin());
99-
assert(v.size() == 2);
100-
assert(v[0] == MoveOnly(2));
101-
assert(v[1] == MoveOnly(3));
102-
}
103-
// When trivial
104-
{
105-
std::vector<TrivialMoveOnly> v;
106-
v.emplace_back(1);
107-
v.emplace_back(2);
108-
v.emplace_back(3);
109-
v.erase(v.begin());
110-
assert(v.size() == 2);
111-
assert(v[0] == TrivialMoveOnly(2));
112-
assert(v[1] == TrivialMoveOnly(3));
30+
{
31+
Vector v(arr, arr + 5);
32+
Iterator it = v.erase(v.cbegin());
33+
assert(v == Vector(arr + 1, arr + 5));
34+
assert(it == v.begin());
35+
assert(is_contiguous_container_asan_correct(v));
36+
}
37+
{
38+
T expected[] = {T(1), T(3), T(4), T(5)};
39+
Vector v(arr, arr + 5);
40+
Iterator it = v.erase(v.cbegin() + 1);
41+
assert(v == Vector(expected, expected + 4));
42+
assert(it == v.begin() + 1);
43+
assert(is_contiguous_container_asan_correct(v));
44+
}
45+
{
46+
T expected[] = {T(1), T(2), T(3), T(4)};
47+
Vector v(arr, arr + 5);
48+
Iterator it = v.erase(v.cbegin() + 4);
49+
assert(v == Vector(expected, expected + 4));
50+
assert(it == v.end());
51+
assert(is_contiguous_container_asan_correct(v));
52+
}
11353
}
11454

115-
#if TEST_STD_VER >= 11
55+
// Make sure vector::erase works with move-only types
11656
{
117-
int a1[] = {1, 2, 3};
118-
std::vector<int, min_allocator<int>> l1(a1, a1 + 3);
119-
std::vector<int, min_allocator<int>>::const_iterator i = l1.begin();
120-
assert(is_contiguous_container_asan_correct(l1));
121-
++i;
122-
std::vector<int, min_allocator<int>>::iterator j = l1.erase(i);
123-
assert(l1.size() == 2);
124-
assert(std::distance(l1.begin(), l1.end()) == 2);
125-
assert(*j == 3);
126-
assert(*l1.begin() == 1);
127-
assert(*std::next(l1.begin()) == 3);
128-
assert(is_contiguous_container_asan_correct(l1));
129-
j = l1.erase(j);
130-
assert(j == l1.end());
131-
assert(l1.size() == 1);
132-
assert(std::distance(l1.begin(), l1.end()) == 1);
133-
assert(*l1.begin() == 1);
134-
assert(is_contiguous_container_asan_correct(l1));
135-
j = l1.erase(l1.begin());
136-
assert(j == l1.end());
137-
assert(l1.size() == 0);
138-
assert(std::distance(l1.begin(), l1.end()) == 0);
139-
assert(is_contiguous_container_asan_correct(l1));
57+
// When non-trivial
58+
{
59+
std::vector<MoveOnly, Allocator<MoveOnly> > v;
60+
v.emplace_back(1);
61+
v.emplace_back(2);
62+
v.emplace_back(3);
63+
v.erase(v.begin());
64+
assert(v.size() == 2);
65+
assert(v[0] == MoveOnly(2));
66+
assert(v[1] == MoveOnly(3));
67+
}
68+
// When trivial
69+
{
70+
std::vector<TrivialMoveOnly, Allocator<TrivialMoveOnly> > v;
71+
v.emplace_back(1);
72+
v.emplace_back(2);
73+
v.emplace_back(3);
74+
v.erase(v.begin());
75+
assert(v.size() == 2);
76+
assert(v[0] == TrivialMoveOnly(2));
77+
assert(v[1] == TrivialMoveOnly(3));
78+
}
14079
}
141-
#endif
80+
}
14281

82+
TEST_CONSTEXPR_CXX20 bool tests() {
83+
tests<std::allocator, int>();
84+
tests<std::allocator, NonTriviallyRelocatable>();
85+
tests<min_allocator, int>();
86+
tests<min_allocator, NonTriviallyRelocatable>();
14387
return true;
14488
}
14589

@@ -163,5 +107,31 @@ int main(int, char**) {
163107
}
164108
#endif
165109

110+
// Make sure we satisfy the complexity requirement in terms of the number of times the assignment
111+
// operator is called.
112+
//
113+
// There is currently ambiguity as to whether this is truly mandated by the Standard, so we only
114+
// test it for libc++.
115+
#ifdef _LIBCPP_VERSION
116+
{
117+
Tracker tracker;
118+
std::vector<TrackedAssignment> v;
119+
120+
// Set up the vector with 5 elements.
121+
for (int i = 0; i != 5; ++i) {
122+
v.emplace_back(&tracker);
123+
}
124+
assert(tracker.copy_assignments == 0);
125+
assert(tracker.move_assignments == 0);
126+
127+
// Erase element [1] from it. Elements [2] [3] [4] should be shifted, so we should
128+
// see 3 move assignments (and nothing else).
129+
v.erase(v.begin() + 1);
130+
assert(v.size() == 4);
131+
assert(tracker.copy_assignments == 0);
132+
assert(tracker.move_assignments == 3);
133+
}
134+
#endif
135+
166136
return 0;
167137
}

0 commit comments

Comments
 (0)