P1144 case study: Moving a fixed_capacity_vector
fixed_capacity_vector
I wrote up this example on Wednesday morning for my presentation of P1144 “Object relocation in terms of move plus destroy” at the Kona committee meeting.
Here’s the complete code on Godbolt
(backup).
Consider a skeleton fixed_capacity_vector
:
template<class T, int Cap>
class fixed_capacity_vector {
int size_ = 0;
alignas(T) char buffer_[Cap][sizeof(T)];
public:
fixed_capacity_vector(fixed_capacity_vector&& rhs) noexcept {
// YOUR CODE GOES HERE
}
~fixed_capacity_vector() {
for (int i=0; i < size_; ++i) {
(*this)[i].~T();
}
}
template<class... Args>
void emplace_back(Args&&... args) {
// assert(size_ < Cap);
new ((void*)buffer_[size_]) T(std::forward<Args>(args)...);
size_ += 1;
}
T *begin() { return (T*)buffer_[0]; }
T *end() { return (T*)buffer_[size_]; }
};
Consider a test harness using this fixed_capacity_vector
.
fixed_capacity_vector<TYPE, 100>
test(fixed_capacity_vector<TYPE, 100> vec) {
vec.emplace_back(nullptr);
return vec;
}
Let type T
be whatever you like — int*
, unique_ptr<int>
, shared_ptr<int>
.
What’s the fastest way to move-construct a fixed_capacity_vector
when it’s
returned from a function, as in this example?
Boost’s static_vector
does this:
fixed_capacity_vector(fixed_capacity_vector&& rhs) noexcept {
size_ = rhs.size_;
std::uninitialized_move(rhs.begin(), rhs.end(), this->begin());
}
I suggest that a faster way would be to do this instead:
fixed_capacity_vector(fixed_capacity_vector&& rhs) noexcept {
size_ = rhs.size_;
std::uninitialized_relocate(rhs.begin(), rhs.end(), this->begin());
rhs.size_ = 0;
}
With my P1144-enabled compiler and library, I compiled my test case nine different ways. The compiler command line looks like this:
clang++ -S test.cpp -std=c++17 -O3 -DTYPE='int*'
wc -l test.s
grep memcpy test.s
To test the “move” strategy, I add -DUSE_RELOCATE_TO_MOVE=0
to the command line.
To test the “relocate (no P1144)” strategy, I add -DUSE_RELOCATE_TO_MOVE=1 -D_LIBCPP_TRIVIALLY_RELOCATABLE=
.
To test the “relocate (with P1144)” strategy, I add -DUSE_RELOCATE_TO_MOVE=1
.
The results:
-DTYPE= |
Strategy | Code size | Uses memcpy? |
---|---|---|---|
int* |
move | 116 | no |
int* |
relocate (no P1144) | 42 | yes |
int* |
relocate (with P1144) | 42 | yes |
unique_ptr<int> |
move | 95 | no |
unique_ptr<int> |
relocate (no P1144) | 96 | no |
unique_ptr<int> |
relocate (with P1144) | 42 | yes |
shared_ptr<int> |
move | 98 | no |
shared_ptr<int> |
relocate (no P1144) | 99 | no |
shared_ptr<int> |
relocate (with P1144) | 45 | yes |
See also:
- “
Quuxplusone/SG14
now hasinplace_vector
” (2023-10-20)