multi_buffer::clear preserves capacity

This commit is contained in:
Vinnie Falco
2019-02-07 14:02:46 -08:00
parent c3125e8358
commit 55d319a9d9
4 changed files with 307 additions and 97 deletions

View File

@@ -5,6 +5,7 @@ Version 211:
* Add stranded_stream * Add stranded_stream
* Add flat_stream * Add flat_stream
* flat_buffer::clear preserves capacity * flat_buffer::clear preserves capacity
* multi_buffer::clear preserves capacity
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -30,40 +30,39 @@ namespace beast {
1 Input and output contained entirely in one element: 1 Input and output contained entirely in one element:
0 out_ 0 out_
|<-------------+------------------------------------------->| |<------+-----------+--------------------------------+----->|
in_pos_ out_pos_ out_end_ in_pos_ out_pos_ out_end_
2 Output contained in first and second elements: 2 Output contained in first and second elements:
out_ out_
|<------+----------+------->| |<----------+-------------->| |<------+-----------+------>| |<-------------------+----->|
in_pos_ out_pos_ out_end_ in_pos_ out_pos_ out_end_
3 Output contained in the second element: 3 Output contained in the second element:
out_ out_
|<------------+------------>| |<----+-------------------->| |<------+------------------>| |<----+--------------+----->|
in_pos_ out_pos_ out_end_ in_pos_ out_pos_ out_end_
4 Output contained in second and third elements: 4 Output contained in second and third elements:
out_ out_
|<-----+-------->| |<-------+------>| |<--------------->| |<------+------->| |<-------+------>| |<---------+----->|
in_pos_ out_pos_ out_end_ in_pos_ out_pos_ out_end_
5 Input sequence is empty: 5 Input sequence is empty:
out_ out_
|<------+------------------>| |<-----------+------------->| |<------+------------------>| |<-------------------+----->|
out_pos_ out_end_ out_pos_ out_end_
in_pos_ in_pos_
6 Output sequence is empty: 6 Output sequence is empty:
out_ out_
@@ -464,7 +463,7 @@ template<class Allocator>
basic_multi_buffer<Allocator>:: basic_multi_buffer<Allocator>::
~basic_multi_buffer() ~basic_multi_buffer()
{ {
delete_list(); destroy(list_);
} }
template<class Allocator> template<class Allocator>
@@ -540,23 +539,23 @@ basic_multi_buffer(
out_ = list_.end(); out_ = list_.end();
copy_from(other); copy_from(other);
other.clear(); other.clear();
other.shrink_to_fit();
return;
} }
else
{ auto const at_end =
auto const at_end = other.out_ == other.list_.end();
other.out_ == other.list_.end(); list_ = std::move(other.list_);
list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_;
out_ = at_end ? list_.end() : other.out_; in_size_ = other.in_size_;
in_size_ = other.in_size_; in_pos_ = other.in_pos_;
in_pos_ = other.in_pos_; out_pos_ = other.out_pos_;
out_pos_ = other.out_pos_; out_end_ = other.out_end_;
out_end_ = other.out_end_; other.in_size_ = 0;
other.in_size_ = 0; other.out_ = other.list_.end();
other.out_ = other.list_.end(); other.in_pos_ = 0;
other.in_pos_ = 0; other.out_pos_ = 0;
other.out_pos_ = 0; other.out_end_ = 0;
other.out_end_ = 0;
}
} }
template<class Allocator> template<class Allocator>
@@ -644,7 +643,6 @@ operator=(
basic_multi_buffer<OtherAlloc> const& other) -> basic_multi_buffer<OtherAlloc> const& other) ->
basic_multi_buffer& basic_multi_buffer&
{ {
clear();
copy_from(other); copy_from(other);
return *this; return *this;
} }
@@ -692,7 +690,7 @@ reserve(std::size_t n)
// the sizeof(element) plus padding // the sizeof(element) plus padding
if(n > alloc_traits::max_size(this->get())) if(n > alloc_traits::max_size(this->get()))
BOOST_THROW_EXCEPTION(std::length_error( BOOST_THROW_EXCEPTION(std::length_error(
"A basic_multi_buffer exceeded the allocator's maximum size")); "A basic_multi_buffer exceeded the allocator's maximum size"));
std::size_t total = in_size_; std::size_t total = in_size_;
if(n <= total) if(n <= total)
return; return;
@@ -717,22 +715,126 @@ reserve(std::size_t n)
template<class Allocator> template<class Allocator>
void void
basic_multi_buffer<Allocator>:: basic_multi_buffer<Allocator>::
shrink_to_fit() noexcept shrink_to_fit()
{ {
if( out_ != list_.end() && // empty list
out_ != list_.iterator_to(list_.back())) if(list_.empty())
return;
// zero readable bytes
if(in_size_ == 0)
{ {
list_type extra; destroy(list_);
extra.splice(extra.end(), list_, list_.clear();
std::next(out_), list_.end()); out_ = list_.end();
for(auto it = extra.begin(); it != extra.end();) in_size_ = 0;
in_pos_ = 0;
out_pos_ = 0;
out_end_ = 0;
#if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK
debug_check();
#endif
return;
}
// one or more unused output buffers
if(out_ != list_.end())
{
if(out_ != list_.iterator_to(list_.back()))
{ {
auto& e = *it++; // unused list
auto const len = sizeof(e) + e.size(); list_type extra;
e.~element(); extra.splice(
alloc_traits::deallocate(this->get(), extra.end(),
reinterpret_cast<char*>(&e), len); list_,
std::next(out_),
list_.end());
destroy(extra);
#if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK
debug_check();
#endif
} }
// unused out_
BOOST_ASSERT(out_ ==
list_.iterator_to(list_.back()));
if(out_pos_ == 0)
{
BOOST_ASSERT(out_ != list_.begin());
auto& e = *out_;
list_.erase(out_);
out_ = list_.end();
destroy(e);
out_end_ = 0;
#if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK
debug_check();
#endif
}
}
auto const replace =
[&](iter pos, element& e)
{
auto it =
list_.insert(pos, e);
auto& e0 = *pos;
list_.erase(pos);
destroy(e0);
return it;
};
// partial last buffer
if(list_.size() > 1 && out_ != list_.end())
{
BOOST_ASSERT(out_ ==
list_.iterator_to(list_.back()));
BOOST_ASSERT(out_pos_ != 0);
auto& e = alloc(out_pos_);
std::memcpy(
e.data(),
out_->data(),
out_pos_);
replace(out_, e);
out_ = list_.end();
out_pos_ = 0;
out_end_ = 0;
#if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK
debug_check();
#endif
}
// partial first buffer
if(in_pos_ != 0)
{
if(out_ != list_.begin())
{
auto const n =
list_.front().size() - in_pos_;
auto& e = alloc(n);
std::memcpy(
e.data(),
list_.front().data() + in_pos_,
n);
replace(list_.begin(), e);
in_pos_ = 0;
}
else
{
BOOST_ASSERT(list_.size() == 1);
BOOST_ASSERT(out_pos_ > in_pos_);
auto const n = out_pos_ - in_pos_;
auto& e = alloc(n);
std::memcpy(
e.data(),
list_.front().data() + in_pos_,
n);
replace(list_.begin(), e);
in_pos_ = 0;
out_ = list_.end();
}
#if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK
debug_check();
#endif
} }
} }
@@ -741,9 +843,7 @@ void
basic_multi_buffer<Allocator>:: basic_multi_buffer<Allocator>::
clear() noexcept clear() noexcept
{ {
delete_list(); out_ = list_.begin();
list_.clear();
out_ = list_.end();
in_size_ = 0; in_size_ = 0;
in_pos_ = 0; in_pos_ = 0;
out_pos_ = 0; out_pos_ = 0;
@@ -813,15 +913,7 @@ prepare(size_type n) ->
BOOST_ASSERT(total <= max_); BOOST_ASSERT(total <= max_);
if(! reuse.empty() || n > 0) if(! reuse.empty() || n > 0)
{ {
for(auto it = reuse.begin(); it != reuse.end();) destroy(reuse);
{
auto& e = *it++;
reuse.erase(list_.iterator_to(e));
auto const len = sizeof(e) + e.size();
e.~element();
alloc_traits::deallocate(this->get(),
reinterpret_cast<char*>(&e), len);
}
if(n > 0) if(n > 0)
{ {
static auto const growth_factor = 2.0f; static auto const growth_factor = 2.0f;
@@ -833,8 +925,7 @@ prepare(size_type n) ->
in_size_ * growth_factor - in_size_), in_size_ * growth_factor - in_size_),
512, 512,
n})); n}));
auto &e = *::new(alloc( auto& e = alloc(size);
sizeof(element) + size)) element(size);
list_.push_back(e); list_.push_back(e);
if(out_ == list_.end()) if(out_ == list_.end())
out_ = list_.iterator_to(e); out_ = list_.iterator_to(e);
@@ -1009,6 +1100,7 @@ move_assign(basic_multi_buffer& other, std::false_type)
{ {
copy_from(other); copy_from(other);
other.clear(); other.clear();
other.shrink_to_fit();
} }
else else
{ {
@@ -1103,27 +1195,45 @@ swap(
template<class Allocator> template<class Allocator>
void void
basic_multi_buffer<Allocator>:: basic_multi_buffer<Allocator>::
delete_list() noexcept destroy(list_type& list) noexcept
{ {
for(auto it = list_.begin(); it != list_.end();) for(auto it = list.begin();
{ it != list.end();)
auto& e = *it++; destroy(*it++);
auto const len = sizeof(e) + e.size();
e.~element();
alloc_traits::deallocate(this->get(),
reinterpret_cast<char*>(&e), len);
}
} }
template<class Allocator> template<class Allocator>
char* void
basic_multi_buffer<Allocator>:: basic_multi_buffer<Allocator>::
alloc(std::size_t n) destroy(const_iter it)
{ {
if(n > alloc_traits::max_size(this->get())) auto& e = list_.erase(it);
destroy(e);
}
template<class Allocator>
void
basic_multi_buffer<Allocator>::
destroy(element& e)
{
auto const len = sizeof(e) + e.size();
e.~element();
alloc_traits::deallocate(this->get(),
reinterpret_cast<char*>(&e), len);
}
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
alloc(std::size_t size) ->
element&
{
if(size > alloc_traits::max_size(this->get()))
BOOST_THROW_EXCEPTION(std::length_error( BOOST_THROW_EXCEPTION(std::length_error(
"A basic_multi_buffer exceeded the allocator's maximum size")); "A basic_multi_buffer exceeded the allocator's maximum size"));
return alloc_traits::allocate(this->get(), n); return *::new(alloc_traits::allocate(
this->get(),
sizeof(element) + size)) element(size);
} }
template<class Allocator> template<class Allocator>

View File

@@ -377,18 +377,14 @@ public:
@par Exception Safety @par Exception Safety
No-throw guarantee. Strong guarantee.
*/ */
void void
shrink_to_fit() noexcept; shrink_to_fit();
/** Deallocate all buffers and reduce capacity to zero. /** Set the size of the readable and writable bytes to zero.
This function deallocates all dynamically allocated
buffers, and reduces the capacity to zero without
affecting the maximum size. The readable and writable
bytes will be empty after the object is cleared.
This clears the buffer without changing capacity.
Buffer sequences previously obtained using @ref data or Buffer sequences previously obtained using @ref data or
@ref prepare become invalid. @ref prepare become invalid.
@@ -541,8 +537,10 @@ private:
void swap(basic_multi_buffer&) noexcept; void swap(basic_multi_buffer&) noexcept;
void swap(basic_multi_buffer&, std::true_type) noexcept; void swap(basic_multi_buffer&, std::true_type) noexcept;
void swap(basic_multi_buffer&, std::false_type) noexcept; void swap(basic_multi_buffer&, std::false_type) noexcept;
void delete_list() noexcept; void destroy(list_type& list) noexcept;
char* alloc(std::size_t n); void destroy(const_iter it);
void destroy(element& e);
element& alloc(std::size_t size);
void debug_check() const; void debug_check() const;
}; };

View File

@@ -53,6 +53,112 @@ public:
buffers_to_string(mb2.data()); buffers_to_string(mb2.data());
} }
void
testShrinkToFit()
{
// empty list
{
multi_buffer b;
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
}
// zero readable bytes
{
multi_buffer b;
b.prepare(512);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
}
{
multi_buffer b;
b.prepare(512);
b.commit(32);
b.consume(32);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
}
{
multi_buffer b;
b.prepare(512);
b.commit(512);
b.prepare(512);
b.clear();
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
}
// unused list
{
multi_buffer b;
b.prepare(512);
b.commit(512);
b.prepare(512);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 512);
BEAST_EXPECT(b.capacity() == 512);
}
{
multi_buffer b;
b.prepare(512);
b.commit(512);
b.prepare(512);
b.prepare(1024);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 512);
BEAST_EXPECT(b.capacity() == 512);
}
// partial last buffer
{
multi_buffer b;
b.prepare(512);
b.commit(512);
b.prepare(512);
b.commit(88);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 600);
BEAST_EXPECT(b.capacity() == 600);
}
// shrink front of first buffer
{
multi_buffer b;
b.prepare(512);
b.commit(512);
b.consume(12);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 500);
BEAST_EXPECT(b.capacity() == 500);
}
// shrink ends of first buffer
{
multi_buffer b;
b.prepare(512);
b.commit(500);
b.consume(100);
b.shrink_to_fit();
BEAST_EXPECT(b.size() == 400);
BEAST_EXPECT(b.capacity() == 400);
}
}
void void
testDynamicBuffer() testDynamicBuffer()
{ {
@@ -496,18 +602,14 @@ public:
// clear // clear
{ {
multi_buffer b; multi_buffer b;
BEAST_EXPECT(b.capacity() == 0);
b.prepare(50); b.prepare(50);
BEAST_EXPECT(b.capacity() >= 50); b.commit(50);
BEAST_EXPECT(b.size() == 50);
BEAST_EXPECT(b.capacity() == 512);
b.clear(); b.clear();
BEAST_EXPECT(b.size() == 0); BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0); BEAST_EXPECT(b.capacity() == 512);
b.prepare(80);
b.commit(30);
BEAST_EXPECT(b.size() == 30);
BEAST_EXPECT(b.capacity() >= 80);
b.clear();
BEAST_EXPECT(b.size() == 0);
BEAST_EXPECT(b.capacity() == 0);
} }
// swap // swap
@@ -714,13 +816,12 @@ public:
void void
run() override run() override
{ {
#if 1
testShrinkToFit();
testDynamicBuffer(); testDynamicBuffer();
testMembers(); testMembers();
testMatrix1(); testMatrix1();
testMatrix2(); testMatrix2();
#if 0
testIterators();
testMutableData<multi_buffer>();
#endif #endif
} }
}; };