[libcxx-commits] [libcxx] [libc++] Optimize copy construction and assignment of __tree (PR #151304)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jul 31 06:38:43 PDT 2025
================
@@ -12,286 +12,251 @@
// map& operator=(const map& m);
-#include <map>
+// We're testing self-assignment here, so don't warn on that
+// ADDITIONAL_COMPILE_FLAGS(gcc-style-warnings): -Wno-self-assign
+
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <iterator>
+#include <map>
#include <vector>
#include "test_macros.h"
#include "../../../test_compare.h"
#include "test_allocator.h"
#include "min_allocator.h"
-#if TEST_STD_VER >= 11
-std::vector<int> ca_allocs;
-std::vector<int> ca_deallocs;
-
template <class T>
-class counting_allocatorT {
-public:
- typedef T value_type;
- int foo{0};
- counting_allocatorT(int f) noexcept : foo(f) {}
+class tracking_allocator {
+ std::vector<void*>* allocs_;
- using propagate_on_container_copy_assignment = std::true_type;
template <class U>
- counting_allocatorT(const counting_allocatorT<U>& other) noexcept {
- foo = other.foo;
- }
- template <class U>
- bool operator==(const counting_allocatorT<U>& other) const noexcept {
- return foo == other.foo;
- }
- template <class U>
- bool operator!=(const counting_allocatorT<U>& other) const noexcept {
- return foo != other.foo;
- }
-
- T* allocate(std::size_t n) const {
- ca_allocs.push_back(foo);
- void* const pv = ::malloc(n * sizeof(T));
- return static_cast<T*>(pv);
- }
- void deallocate(T* p, std::size_t) const noexcept {
- ca_deallocs.push_back(foo);
- free(p);
- }
-};
+ friend class tracking_allocator;
-template <class T>
-class counting_allocatorF {
public:
- typedef T value_type;
- int foo{0};
- counting_allocatorF(int f) noexcept : foo(f) {}
+ using value_type = T;
+ using propagate_on_container_copy_assignment = std::true_type;
+
+ tracking_allocator(std::vector<void*>& allocs) : allocs_(&allocs) {}
- using propagate_on_container_copy_assignment = std::false_type;
- template <class U>
- counting_allocatorF(const counting_allocatorF<U>& other) noexcept {
- foo = other.foo;
- }
template <class U>
- bool operator==(const counting_allocatorF<U>& other) const noexcept {
- return foo == other.foo;
+ tracking_allocator(const tracking_allocator<U>& other) : allocs_(other.allocs_) {}
+
+ T* allocate(std::size_t n) {
+ T* allocation = std::allocator<T>().allocate(n);
+ allocs_->push_back(allocation);
+ return allocation;
}
- template <class U>
- bool operator!=(const counting_allocatorF<U>& other) const noexcept {
- return foo != other.foo;
+
+ void deallocate(T* ptr, std::size_t n) noexcept {
+ auto res = std::remove(allocs_->begin(), allocs_->end(), ptr);
+ assert(res != allocs_->end() && "Trying to deallocate memory from different allocator?");
+ allocs_->erase(res);
+ std::allocator<T>().deallocate(ptr, n);
}
- T* allocate(std::size_t n) const {
- ca_allocs.push_back(foo);
- void* const pv = ::malloc(n * sizeof(T));
- return static_cast<T*>(pv);
+ friend bool operator==(const tracking_allocator& lhs, const tracking_allocator& rhs) {
+ return lhs.allocs_ == rhs.allocs_;
}
- void deallocate(T* p, std::size_t) const noexcept {
- ca_deallocs.push_back(foo);
- free(p);
+
+ friend bool operator!=(const tracking_allocator& lhs, const tracking_allocator& rhs) {
+ return lhs.allocs_ != rhs.allocs_;
}
};
-bool balanced_allocs() {
- std::vector<int> temp1, temp2;
-
- std::printf("Allocations = %zu, deallocations = %zu\n", ca_allocs.size(), ca_deallocs.size());
- if (ca_allocs.size() != ca_deallocs.size())
- return false;
-
- temp1 = ca_allocs;
- std::sort(temp1.begin(), temp1.end());
- temp2.clear();
- std::unique_copy(temp1.begin(), temp1.end(), std::back_inserter<std::vector<int>>(temp2));
- std::printf("There were %zu different allocators\n", temp2.size());
-
- for (std::vector<int>::const_iterator it = temp2.begin(); it != temp2.end(); ++it) {
- std::ptrdiff_t const allocs = std::count(ca_allocs.begin(), ca_allocs.end(), *it);
- std::ptrdiff_t const deallocs = std::count(ca_deallocs.begin(), ca_deallocs.end(), *it);
- std::printf("%d: %td vs %td\n", *it, allocs, deallocs);
- if (allocs != deallocs)
- return false;
+struct NoOp {
+ void operator()() {}
+};
+
+template <class Alloc, class AllocatorInvariant = NoOp>
+void test_alloc(const Alloc& lhs_alloc = Alloc(),
+ const Alloc& rhs_alloc = Alloc(),
+ AllocatorInvariant check_alloc_invariant = NoOp()) {
+ { // Test empty/non-empy map combinations
+ { // assign from a non-empty container into an empty one
+ using V = std::pair<const int, int>;
+ V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+ std::map<int, int, std::less<int>, Alloc> orig(begin(arr), end(arr), rhs_alloc);
+ std::map<int, int, std::less<int>, Alloc> copy(lhs_alloc);
+ copy = orig;
+ assert(copy.size() == 3);
+ assert(*std::next(copy.begin(), 0) == V(1, 1));
+ assert(*std::next(copy.begin(), 1) == V(2, 3));
+ assert(*std::next(copy.begin(), 2) == V(3, 6));
+ assert(std::next(copy.begin(), 3) == copy.end());
+
+ // Check that orig is still what is expected
+ assert(orig.size() == 3);
+ assert(*std::next(orig.begin(), 0) == V(1, 1));
+ assert(*std::next(orig.begin(), 1) == V(2, 3));
+ assert(*std::next(orig.begin(), 2) == V(3, 6));
+ assert(std::next(orig.begin(), 3) == orig.end());
+ }
+ check_alloc_invariant();
+ { // assign from an empty container into an empty one
+ std::map<int, int, std::less<int>, Alloc> orig(rhs_alloc);
+ std::map<int, int, std::less<int>, Alloc> copy(lhs_alloc);
+ copy = orig;
+ assert(copy.size() == 0);
+ assert(copy.begin() == copy.end());
+
+ // Check that orig is still what is expected
+ assert(orig.size() == 0);
+ assert(orig.begin() == orig.end());
+ }
+ check_alloc_invariant();
+ { // assign from an empty container into a non-empty one
+ using V = std::pair<const int, int>;
+ V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+ std::map<int, int, std::less<int>, Alloc> orig(rhs_alloc);
+ std::map<int, int, std::less<int>, Alloc> copy(begin(arr), end(arr), rhs_alloc);
+ copy = orig;
+ assert(copy.size() == 0);
+ assert(copy.begin() == copy.end());
+
+ // Check that orig is still what is expected
+ assert(orig.size() == 0);
+ assert(orig.begin() == orig.end());
+ }
}
- temp1 = ca_allocs;
- std::sort(temp1.begin(), temp1.end());
- temp2.clear();
- std::unique_copy(temp1.begin(), temp1.end(), std::back_inserter<std::vector<int>>(temp2));
- std::printf("There were %zu different (de)allocators\n", temp2.size());
-
- for (std::vector<int>::const_iterator it = ca_deallocs.begin(); it != ca_deallocs.end(); ++it) {
- std::ptrdiff_t const allocs = std::count(ca_allocs.begin(), ca_allocs.end(), *it);
- std::ptrdiff_t const deallocs = std::count(ca_deallocs.begin(), ca_deallocs.end(), *it);
- std::printf("%d: %td vs %td\n", *it, allocs, deallocs);
- if (allocs != deallocs)
- return false;
+ { // Ensure that self-assignment works correctly
+ { // with a non-empty map
+ using V = std::pair<const int, int>;
+ V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+ std::map<int, int, std::less<int>, Alloc> orig(begin(arr), end(arr), rhs_alloc);
+ orig = orig;
+
+ assert(orig.size() == 3);
+ assert(*std::next(orig.begin(), 0) == V(1, 1));
+ assert(*std::next(orig.begin(), 1) == V(2, 3));
+ assert(*std::next(orig.begin(), 2) == V(3, 6));
+ assert(std::next(orig.begin(), 3) == orig.end());
+ }
+ { // with an empty map
+ std::map<int, int, std::less<int>, Alloc> orig(rhs_alloc);
+ orig = orig;
+
+ assert(orig.size() == 0);
+ assert(orig.begin() == orig.end());
+ }
}
- return true;
-}
-#endif
+ { // check assignment into a non-empty map
+ check_alloc_invariant();
+ { // LHS already contains, but fewer than needed
----------------
ldionne wrote:
```suggestion
{ // LHS already contains, but fewer than the incoming map
```
Below too.
Otherwise, you're referring too much to your optimization and it doesn't make sense out of context.
https://github.com/llvm/llvm-project/pull/151304
More information about the libcxx-commits
mailing list