Skip to content

Commit 8683b21

Browse files
committed
Speed-up and refactor move-assignment operator for vector<bool>
1 parent 8b02d80 commit 8683b21

File tree

5 files changed

+260
-104
lines changed

5 files changed

+260
-104
lines changed

libcxx/include/__vector/vector_bool.h

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ class _LIBCPP_TEMPLATE_VIS vector<bool, _Allocator> {
398398
__guard.__complete();
399399
}
400400

401+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __copy_by_words(const vector& __v);
402+
401403
template <class _Iterator, class _Sentinel>
402404
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __assign_with_sentinel(_Iterator __first, _Sentinel __last);
403405

@@ -695,18 +697,25 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 vector<bool, _Allocator>::vector(const vector& __v
695697
}
696698
}
697699

700+
// This function copies each storage word as a whole, which is substantially more efficient than copying each bit
701+
// individually
702+
template <class _Allocator>
703+
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void vector<bool, _Allocator>::__copy_by_words(const vector& __v) {
704+
if (__v.__size_) {
705+
if (__v.__size_ > capacity()) {
706+
__vdeallocate();
707+
__vallocate(__v.__size_);
708+
}
709+
std::copy(__v.__begin_, __v.__begin_ + __external_cap_to_internal(__v.__size_), __begin_);
710+
}
711+
__size_ = __v.__size_;
712+
}
713+
698714
template <class _Allocator>
699715
_LIBCPP_CONSTEXPR_SINCE_CXX20 vector<bool, _Allocator>& vector<bool, _Allocator>::operator=(const vector& __v) {
700716
if (this != std::addressof(__v)) {
701717
__copy_assign_alloc(__v);
702-
if (__v.__size_) {
703-
if (__v.__size_ > capacity()) {
704-
__vdeallocate();
705-
__vallocate(__v.__size_);
706-
}
707-
std::copy(__v.__begin_, __v.__begin_ + __external_cap_to_internal(__v.__size_), __begin_);
708-
}
709-
__size_ = __v.__size_;
718+
__copy_by_words(__v);
710719
}
711720
return *this;
712721
}
@@ -754,7 +763,7 @@ vector<bool, _Allocator>::operator=(vector&& __v)
754763
template <class _Allocator>
755764
_LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<bool, _Allocator>::__move_assign(vector& __c, false_type) {
756765
if (__alloc_ != __c.__alloc_)
757-
assign(__c.begin(), __c.end());
766+
__copy_by_words(__c);
758767
else
759768
__move_assign(__c, true_type());
760769
}

libcxx/test/benchmarks/containers/ContainerBenchmarks.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ void BM_Assignment(benchmark::State& st, Container) {
5151
}
5252
}
5353

54+
template <class Container, class Allocator>
55+
void BM_Move_Assignment(benchmark::State& st, Container, Allocator) {
56+
auto size = st.range(0);
57+
Container c1(Allocator{1});
58+
Container c2(Allocator{2});
59+
c1.reserve(size);
60+
c2.resize(size);
61+
for (auto _ : st) {
62+
c1 = std::move(c2);
63+
DoNotOptimizeData(c1);
64+
DoNotOptimizeData(c2);
65+
}
66+
}
67+
5468
template <std::size_t... sz, typename Container, typename GenInputs>
5569
void BM_AssignInputIterIter(benchmark::State& st, Container c, GenInputs gen) {
5670
auto v = gen(1, sz...);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
11+
#include <cstdint>
12+
#include <cstdlib>
13+
#include <cstring>
14+
#include <deque>
15+
#include <functional>
16+
#include <memory>
17+
#include <string>
18+
#include <vector>
19+
20+
#include "benchmark/benchmark.h"
21+
#include "ContainerBenchmarks.h"
22+
#include "../GenerateInput.h"
23+
24+
using namespace ContainerBenchmarks;
25+
26+
template <typename T, typename SIZE_TYPE = std::size_t, typename DIFF_TYPE = std::ptrdiff_t>
27+
class CustomSizedAllocator {
28+
template <typename U, typename Sz, typename Diff>
29+
friend class CustomSizedAllocator;
30+
31+
public:
32+
using value_type = T;
33+
using size_type = SIZE_TYPE;
34+
using difference_type = DIFF_TYPE;
35+
using propagate_on_container_swap = std::true_type;
36+
37+
explicit CustomSizedAllocator(int i = 0) : data_(i) {}
38+
39+
template <typename U, typename Sz, typename Diff>
40+
constexpr CustomSizedAllocator(const CustomSizedAllocator<U, Sz, Diff>& a) noexcept : data_(a.data_) {}
41+
42+
constexpr T* allocate(size_type n) {
43+
if (n > max_size())
44+
throw std::bad_array_new_length();
45+
return std::allocator<T>().allocate(n);
46+
}
47+
48+
constexpr void deallocate(T* p, size_type n) noexcept { std::allocator<T>().deallocate(p, n); }
49+
50+
constexpr size_type max_size() const noexcept { return std::numeric_limits<size_type>::max() / sizeof(value_type); }
51+
52+
int get() { return data_; }
53+
54+
private:
55+
int data_;
56+
57+
constexpr friend bool operator==(const CustomSizedAllocator& a, const CustomSizedAllocator& b) {
58+
return a.data_ == b.data_;
59+
}
60+
constexpr friend bool operator!=(const CustomSizedAllocator& a, const CustomSizedAllocator& b) {
61+
return a.data_ != b.data_;
62+
}
63+
};
64+
65+
BENCHMARK_CAPTURE(BM_Move_Assignment,
66+
vector_bool_uint32_t,
67+
std::vector<bool, CustomSizedAllocator<bool, std::uint32_t, std::int32_t>>{},
68+
CustomSizedAllocator<bool, std::uint32_t, std::int32_t>{})
69+
->Arg(5140480);
70+
71+
BENCHMARK_CAPTURE(BM_Move_Assignment,
72+
vector_bool_uint64_t,
73+
std::vector<bool, CustomSizedAllocator<bool, std::uint64_t, std::int64_t>>{},
74+
CustomSizedAllocator<bool, std::uint64_t, std::int64_t>{})
75+
->Arg(5140480);
76+
77+
BENCHMARK_MAIN();

libcxx/test/std/containers/sequences/vector.bool/assign_copy.pass.cpp

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,65 @@
1010

1111
// vector& operator=(const vector& c);
1212

13-
#include <vector>
1413
#include <cassert>
15-
#include "test_macros.h"
16-
#include "test_allocator.h"
14+
#include <vector>
15+
1716
#include "min_allocator.h"
17+
#include "test_allocator.h"
18+
#include "test_macros.h"
1819

19-
TEST_CONSTEXPR_CXX20 bool tests()
20-
{
21-
{
22-
std::vector<bool, test_allocator<bool> > l(3, true, test_allocator<bool>(5));
23-
std::vector<bool, test_allocator<bool> > l2(l, test_allocator<bool>(3));
24-
l2 = l;
25-
assert(l2 == l);
26-
assert(l2.get_allocator() == test_allocator<bool>(3));
27-
}
28-
{
29-
std::vector<bool, other_allocator<bool> > l(3, true, other_allocator<bool>(5));
30-
std::vector<bool, other_allocator<bool> > l2(l, other_allocator<bool>(3));
31-
l2 = l;
32-
assert(l2 == l);
33-
assert(l2.get_allocator() == other_allocator<bool>(5));
34-
}
20+
TEST_CONSTEXPR_CXX20 bool tests() {
21+
// Test with insufficient space where reallocation occurs during assignment
22+
{
23+
std::vector<bool, test_allocator<bool> > l(3, true, test_allocator<bool>(5));
24+
std::vector<bool, test_allocator<bool> > l2(l, test_allocator<bool>(3));
25+
l2 = l;
26+
assert(l2 == l);
27+
assert(l2.get_allocator() == test_allocator<bool>(3));
28+
}
29+
{
30+
std::vector<bool, other_allocator<bool> > l(3, true, other_allocator<bool>(5));
31+
std::vector<bool, other_allocator<bool> > l2(l, other_allocator<bool>(3));
32+
l2 = l;
33+
assert(l2 == l);
34+
assert(l2.get_allocator() == other_allocator<bool>(5));
35+
}
3536
#if TEST_STD_VER >= 11
36-
{
37-
std::vector<bool, min_allocator<bool> > l(3, true, min_allocator<bool>());
38-
std::vector<bool, min_allocator<bool> > l2(l, min_allocator<bool>());
39-
l2 = l;
40-
assert(l2 == l);
41-
assert(l2.get_allocator() == min_allocator<bool>());
42-
}
37+
{
38+
std::vector<bool, min_allocator<bool> > l(3, true, min_allocator<bool>());
39+
std::vector<bool, min_allocator<bool> > l2(l, min_allocator<bool>());
40+
l2 = l;
41+
assert(l2 == l);
42+
assert(l2.get_allocator() == min_allocator<bool>());
43+
}
4344
#endif
4445

45-
return true;
46+
// Test with sufficient size where no reallocation occurs during assignment
47+
{
48+
std::vector<bool, test_allocator<bool> > l(4, true, test_allocator<bool>(5));
49+
std::vector<bool, test_allocator<bool> > l2(5, false, test_allocator<bool>(3));
50+
l2 = l;
51+
assert(l2 == l);
52+
assert(l2.get_allocator() == test_allocator<bool>(3));
53+
}
54+
55+
// Test with sufficient spare space where no reallocation occurs during assignment
56+
{
57+
std::vector<bool, test_allocator<bool> > l(4, true, test_allocator<bool>(5));
58+
std::vector<bool, test_allocator<bool> > l2(2, false, test_allocator<bool>(3));
59+
l2.reserve(5);
60+
l2 = l;
61+
assert(l2 == l);
62+
assert(l2.get_allocator() == test_allocator<bool>(3));
63+
}
64+
65+
return true;
4666
}
4767

48-
int main(int, char**)
49-
{
50-
tests();
68+
int main(int, char**) {
69+
tests();
5170
#if TEST_STD_VER > 17
52-
static_assert(tests());
71+
static_assert(tests());
5372
#endif
54-
return 0;
73+
return 0;
5574
}

0 commit comments

Comments
 (0)