From 9938700d893c47e89104673d8fece65e40818bb2 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 14 Nov 2024 17:26:24 +0100 Subject: [PATCH 1/3] [libc++] Improve the tests for vector::erase 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. --- libcxx/test/benchmarks/ContainerBenchmarks.h | 30 ++ libcxx/test/benchmarks/deque.bench.cpp | 11 + .../benchmarks/vector_operations.bench.cpp | 10 + .../vector/vector.modifiers/common.h | 86 ++++++ .../vector.modifiers/erase_iter.pass.cpp | 196 ++++++------- .../vector.modifiers/erase_iter_iter.pass.cpp | 269 ++++++++++-------- 6 files changed, 361 insertions(+), 241 deletions(-) create mode 100644 libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h diff --git a/libcxx/test/benchmarks/ContainerBenchmarks.h b/libcxx/test/benchmarks/ContainerBenchmarks.h index 742c848328604c..38e11777f488b7 100644 --- a/libcxx/test/benchmarks/ContainerBenchmarks.h +++ b/libcxx/test/benchmarks/ContainerBenchmarks.h @@ -11,6 +11,8 @@ #define BENCHMARK_CONTAINER_BENCHMARKS_H #include +#include +#include #include "Utilities.h" #include "benchmark/benchmark.h" @@ -149,6 +151,34 @@ void BM_EmplaceDuplicate(benchmark::State& st, Container c, GenInputs gen) { } } +template +void BM_erase_iter_in_middle(benchmark::State& st, Container, GenInputs gen) { + auto in = gen(st.range(0)); + Container c(in.begin(), in.end()); + assert(c.size() > 2); + for (auto _ : st) { + auto mid = std::next(c.begin(), c.size() / 2); + auto tmp = *mid; + auto result = c.erase(mid); // erase an element in the middle + benchmark::DoNotOptimize(result); + c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container + } +} + +template +void BM_erase_iter_at_start(benchmark::State& st, Container, GenInputs gen) { + auto in = gen(st.range(0)); + Container c(in.begin(), in.end()); + assert(c.size() > 2); + for (auto _ : st) { + auto it = c.begin(); + auto tmp = *it; + auto result = c.erase(it); // erase the first element + benchmark::DoNotOptimize(result); + c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container + } +} + template void BM_Find(benchmark::State& st, Container c, GenInputs gen) { auto in = gen(st.range(0)); diff --git a/libcxx/test/benchmarks/deque.bench.cpp b/libcxx/test/benchmarks/deque.bench.cpp index b8f3b76dd27ee6..ab0ba96b12ffca 100644 --- a/libcxx/test/benchmarks/deque.bench.cpp +++ b/libcxx/test/benchmarks/deque.bench.cpp @@ -9,6 +9,7 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 #include +#include #include "benchmark/benchmark.h" @@ -41,4 +42,14 @@ BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_size_t, std::deque{}, get BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_string, std::deque{}, getRandomStringInputs) ->Arg(TestNumInputs); +BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_int, std::deque{}, getRandomIntegerInputs) + ->Range(TestNumInputs, TestNumInputs * 10); +BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_string, std::deque{}, getRandomStringInputs) + ->Range(TestNumInputs, TestNumInputs * 10); + +BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_int, std::deque{}, getRandomIntegerInputs) + ->Range(TestNumInputs, TestNumInputs * 10); +BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_string, std::deque{}, getRandomStringInputs) + ->Range(TestNumInputs, TestNumInputs * 10); + BENCHMARK_MAIN(); diff --git a/libcxx/test/benchmarks/vector_operations.bench.cpp b/libcxx/test/benchmarks/vector_operations.bench.cpp index ce8ab233fc9817..1855861263324d 100644 --- a/libcxx/test/benchmarks/vector_operations.bench.cpp +++ b/libcxx/test/benchmarks/vector_operations.bench.cpp @@ -54,6 +54,16 @@ BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_string, std::vector BENCHMARK_CAPTURE(BM_Pushback_no_grow, vector_int, std::vector{})->Arg(TestNumInputs); +BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_int, std::vector{}, getRandomIntegerInputs) + ->Range(TestNumInputs, TestNumInputs * 10); +BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_string, std::vector{}, getRandomStringInputs) + ->Range(TestNumInputs, TestNumInputs * 10); + +BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_int, std::vector{}, getRandomIntegerInputs) + ->Range(TestNumInputs, TestNumInputs * 10); +BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_string, std::vector{}, getRandomStringInputs) + ->Range(TestNumInputs, TestNumInputs * 10); + template void bm_grow(benchmark::State& state) { for (auto _ : state) { diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h b/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h new file mode 100644 index 00000000000000..72cd47a50b2c0d --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/common.h @@ -0,0 +1,86 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H +#define TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H + +#include "test_macros.h" + +#include // for __libcpp_is_trivially_relocatable + +#ifndef TEST_HAS_NO_EXCEPTIONS +struct Throws { + Throws() : v_(0) {} + Throws(int v) : v_(v) {} + Throws(const Throws& rhs) : v_(rhs.v_) { + if (sThrows) + throw 1; + } + Throws(Throws&& rhs) : v_(rhs.v_) { + if (sThrows) + throw 1; + } + Throws& operator=(const Throws& rhs) { + v_ = rhs.v_; + return *this; + } + Throws& operator=(Throws&& rhs) { + v_ = rhs.v_; + return *this; + } + int v_; + static bool sThrows; +}; + +bool Throws::sThrows = false; +#endif + +struct Tracker { + int copy_assignments = 0; + int move_assignments = 0; +}; + +struct TrackedAssignment { + Tracker* tracker_; + TEST_CONSTEXPR_CXX14 explicit TrackedAssignment(Tracker* tracker) : tracker_(tracker) {} + + TrackedAssignment(TrackedAssignment const&) = default; + TrackedAssignment(TrackedAssignment&&) = default; + + TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment const&) { + tracker_->copy_assignments++; + return *this; + } + TEST_CONSTEXPR_CXX14 TrackedAssignment& operator=(TrackedAssignment&&) { + tracker_->move_assignments++; + return *this; + } +}; + +struct NonTriviallyRelocatable { + int value_; + TEST_CONSTEXPR NonTriviallyRelocatable() : value_(0) {} + TEST_CONSTEXPR explicit NonTriviallyRelocatable(int v) : value_(v) {} + TEST_CONSTEXPR NonTriviallyRelocatable(NonTriviallyRelocatable const& other) : value_(other.value_) {} + TEST_CONSTEXPR NonTriviallyRelocatable(NonTriviallyRelocatable&& other) : value_(other.value_) {} + TEST_CONSTEXPR_CXX14 NonTriviallyRelocatable& operator=(NonTriviallyRelocatable const& other) { + value_ = other.value_; + return *this; + } + TEST_CONSTEXPR_CXX14 NonTriviallyRelocatable& operator=(NonTriviallyRelocatable&& other) { + value_ = other.value_; + return *this; + } + + TEST_CONSTEXPR_CXX14 friend bool operator==(NonTriviallyRelocatable const& a, NonTriviallyRelocatable const& b) { + return a.value_ == b.value_; + } +}; +LIBCPP_STATIC_ASSERT(!std::__libcpp_is_trivially_relocatable::value, ""); + +#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_VECTOR_MODIFIERS_COMMON_H diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp index 549f29a8f7ba11..4a089fd7a4c4fc 100644 --- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp @@ -11,135 +11,80 @@ // iterator erase(const_iterator position); #include -#include #include +#include #include "asan_testing.h" +#include "common.h" #include "min_allocator.h" #include "MoveOnly.h" #include "test_macros.h" -#ifndef TEST_HAS_NO_EXCEPTIONS -struct Throws { - Throws() : v_(0) {} - Throws(int v) : v_(v) {} - Throws(const Throws& rhs) : v_(rhs.v_) { - if (sThrows) - throw 1; - } - Throws(Throws&& rhs) : v_(rhs.v_) { - if (sThrows) - throw 1; - } - Throws& operator=(const Throws& rhs) { - v_ = rhs.v_; - return *this; - } - Throws& operator=(Throws&& rhs) { - v_ = rhs.v_; - return *this; - } - int v_; - static bool sThrows; -}; - -bool Throws::sThrows = false; -#endif - -TEST_CONSTEXPR_CXX20 bool tests() { - { - int a1[] = {1, 2, 3, 4, 5}; - std::vector l1(a1, a1 + 5); - l1.erase(l1.begin()); - assert(is_contiguous_container_asan_correct(l1)); - assert(l1 == std::vector(a1 + 1, a1 + 5)); - } +template