Skip to content

Commit 5d9ff8f

Browse files
authored
Optimizes message memmoves (#501)
Changes read_buffer to always use powers of two when growing in size Optimizes message_reader to avoid memmoves when the data to move is big close #462
1 parent c20a041 commit 5d9ff8f

File tree

8 files changed

+349
-16
lines changed

8 files changed

+349
-16
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
8+
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP
9+
#define BOOST_MYSQL_IMPL_INTERNAL_NEXT_POWER_OF_TWO_HPP
10+
11+
#include <boost/assert.hpp>
12+
13+
#include <type_traits>
14+
#include <limits>
15+
16+
namespace boost {
17+
namespace mysql {
18+
namespace detail {
19+
20+
template<int Shift, class UnsignedInt, bool Continue>
21+
struct recursive_shift_or;
22+
23+
// Apply shift and continue to the next power of 2
24+
template<int Shift, class UnsignedInt>
25+
struct recursive_shift_or<Shift, UnsignedInt, true>
26+
{
27+
static void apply(UnsignedInt& n)
28+
{
29+
n |= n >> Shift;
30+
recursive_shift_or<Shift * 2, UnsignedInt, (Shift * 2 < sizeof(UnsignedInt) * 8)>::apply(n);
31+
}
32+
};
33+
34+
// Stop recursion when shift exceeds type width
35+
template<int Shift, class UnsignedInt>
36+
struct recursive_shift_or<Shift, UnsignedInt, false>
37+
{
38+
static void apply(UnsignedInt&) {}
39+
};
40+
41+
// Returns the smallest power of two greater than or equal to n.
42+
// Precondition: n must not exceed the largest power of two that fits
43+
// in UnsignedInt. For example:
44+
// - uint8_t: n <= 128 (2^7)
45+
// - uint16_t: n <= 32768 (2^15)
46+
// - uint32_t: n <= 2147483648 (2^31)
47+
// - uint64_t: n <= 9223372036854775808 (2^63)
48+
//
49+
// Passing a larger value results in undefined behavior (overflow).
50+
// In debug builds, this is caught by BOOST_ASSERT.
51+
template<class UnsignedInt>
52+
UnsignedInt next_power_of_two(UnsignedInt n) noexcept
53+
{
54+
static_assert(std::is_unsigned<UnsignedInt>::value, "");
55+
// Assert overflow (if value is bigger than maximum power)
56+
BOOST_ASSERT(!(n > (std::numeric_limits<UnsignedInt>::max() >> 1) + 1));
57+
if (n == 0) return 1;
58+
n--;
59+
// Fill all lower bits
60+
recursive_shift_or<1, UnsignedInt, (1 < sizeof(UnsignedInt) * 8)>::apply(n);
61+
return n + 1;
62+
}
63+
64+
} // namespace detail
65+
} // namespace mysql
66+
} // namespace boost
67+
68+
#endif

include/boost/mysql/impl/internal/sansio/message_reader.hpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,17 @@ class message_reader
9393
BOOST_ATTRIBUTE_NODISCARD
9494
error_code prepare_buffer()
9595
{
96-
buffer_.remove_reserved();
96+
constexpr std::size_t small_move_threshold = 1024;
97+
const std::size_t occupied_space = buffer_.pending_size() + buffer_.current_message_size();
98+
// Compact the buffer (remove reserved area) if one of the following holds:
99+
// 1. The cost of memmove is low: active data (current_message + pending)
100+
// is small enough to make the copy cheap.
101+
// 2. Compaction could prevent a reallocation.
102+
if (occupied_space <= small_move_threshold ||
103+
(state_.required_size > buffer_.free_size()))
104+
{
105+
buffer_.remove_reserved();
106+
}
97107
auto ec = buffer_.grow_to_fit(state_.required_size);
98108
if (ec)
99109
return ec;

include/boost/mysql/impl/internal/sansio/read_buffer.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <boost/mysql/client_errc.hpp>
1212
#include <boost/mysql/error_code.hpp>
1313

14+
#include <boost/mysql/impl/internal/next_power_of_two.hpp>
15+
1416
#include <boost/assert.hpp>
1517
#include <boost/config.hpp>
1618
#include <boost/core/span.hpp>
@@ -138,14 +140,20 @@ class read_buffer
138140
}
139141

140142
// Makes sure the free size is at least n bytes long; resizes the buffer if required
143+
// Buffer grows to power of two, unless limited by max_size
141144
BOOST_ATTRIBUTE_NODISCARD
142145
error_code grow_to_fit(std::size_t n)
143146
{
144147
if (free_size() < n)
145148
{
146-
std::size_t new_size = buffer_.size() + n - free_size();
149+
std::size_t required_size = buffer_.size() + n - free_size();
150+
std::size_t new_size = next_power_of_two<std::size_t>(required_size);
147151
if (new_size > max_size_)
148-
return client_errc::max_buffer_size_exceeded;
152+
{
153+
new_size = required_size;
154+
if (new_size > max_size_)
155+
return client_errc::max_buffer_size_exceeded;
156+
}
149157
buffer_.resize(new_size);
150158
}
151159
return error_code();

test/unit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ add_executable(
7878

7979
test/impl/dt_to_string.cpp
8080
test/impl/ssl_context_with_default.cpp
81+
test/impl/next_power_of_two.cpp
8182
test/impl/variant_stream.cpp
8283

8384
test/spotchecks/connection_use_after_move.cpp

test/unit/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ run
8787

8888
test/impl/dt_to_string.cpp
8989
test/impl/ssl_context_with_default.cpp
90+
test/impl/next_power_of_two.cpp
9091
test/impl/variant_stream.cpp
9192

9293
test/spotchecks/connection_use_after_move.cpp
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//
2+
// Copyright (c) 2026 Vladislav Soulgard (vsoulgard at gmail dot com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
8+
#include <boost/mysql/impl/internal/next_power_of_two.hpp>
9+
10+
#include <boost/test/unit_test.hpp>
11+
12+
#include <cstdint>
13+
14+
using namespace boost::mysql::detail;
15+
16+
BOOST_AUTO_TEST_SUITE(test_next_power_of_two)
17+
18+
BOOST_AUTO_TEST_CASE(basic)
19+
{
20+
// n = 0 (special case)
21+
BOOST_TEST(next_power_of_two<std::size_t>(0u) == 1u);
22+
23+
// n is already power of two
24+
BOOST_TEST(next_power_of_two<std::size_t>(1u) == 1u);
25+
BOOST_TEST(next_power_of_two<std::size_t>(2u) == 2u);
26+
BOOST_TEST(next_power_of_two<std::size_t>(4u) == 4u);
27+
BOOST_TEST(next_power_of_two<std::size_t>(8u) == 8u);
28+
BOOST_TEST(next_power_of_two<std::size_t>(16u) == 16u);
29+
BOOST_TEST(next_power_of_two<std::size_t>(32u) == 32u);
30+
BOOST_TEST(next_power_of_two<std::size_t>(64u) == 64u);
31+
BOOST_TEST(next_power_of_two<std::size_t>(128u) == 128u);
32+
33+
// n just below power of two
34+
BOOST_TEST(next_power_of_two<std::size_t>(3u) == 4u);
35+
BOOST_TEST(next_power_of_two<std::size_t>(7u) == 8u);
36+
BOOST_TEST(next_power_of_two<std::size_t>(15u) == 16u);
37+
BOOST_TEST(next_power_of_two<std::size_t>(31u) == 32u);
38+
BOOST_TEST(next_power_of_two<std::size_t>(63u) == 64u);
39+
BOOST_TEST(next_power_of_two<std::size_t>(127u) == 128u);
40+
41+
// n just above power of two
42+
BOOST_TEST(next_power_of_two<std::size_t>(5u) == 8u);
43+
BOOST_TEST(next_power_of_two<std::size_t>(9u) == 16u);
44+
BOOST_TEST(next_power_of_two<std::size_t>(17u) == 32u);
45+
BOOST_TEST(next_power_of_two<std::size_t>(33u) == 64u);
46+
BOOST_TEST(next_power_of_two<std::size_t>(65u) == 128u);
47+
BOOST_TEST(next_power_of_two<std::size_t>(129u) == 256u);
48+
49+
// n is random value
50+
BOOST_TEST(next_power_of_two<std::size_t>(6u) == 8u);
51+
BOOST_TEST(next_power_of_two<std::size_t>(13u) == 16u);
52+
BOOST_TEST(next_power_of_two<std::size_t>(21u) == 32u);
53+
BOOST_TEST(next_power_of_two<std::size_t>(45u) == 64u);
54+
BOOST_TEST(next_power_of_two<std::size_t>(89u) == 128u);
55+
BOOST_TEST(next_power_of_two<std::size_t>(200u) == 256u);
56+
BOOST_TEST(next_power_of_two<std::size_t>(300u) == 512u);
57+
BOOST_TEST(next_power_of_two<std::size_t>(400u) == 512u);
58+
BOOST_TEST(next_power_of_two<std::size_t>(505u) == 512u);
59+
BOOST_TEST(next_power_of_two<std::size_t>(888u) == 1024u);
60+
}
61+
62+
BOOST_AUTO_TEST_CASE(different_types)
63+
{
64+
// uint8_t
65+
BOOST_TEST(next_power_of_two<std::uint8_t>(0u) == 1u);
66+
BOOST_TEST(next_power_of_two<std::uint8_t>(62u) == 64u);
67+
BOOST_TEST(next_power_of_two<std::uint8_t>(100u) == 128u);
68+
BOOST_TEST(next_power_of_two<std::uint8_t>(128u) == 128u);
69+
70+
// uint16_t
71+
BOOST_TEST(next_power_of_two<std::uint16_t>(0u) == 1u);
72+
BOOST_TEST(next_power_of_two<std::uint16_t>(1000u) == 1024u);
73+
BOOST_TEST(next_power_of_two<std::uint16_t>(16383u) == 16384u);
74+
BOOST_TEST(next_power_of_two<std::uint16_t>(32768u) == 32768u);
75+
76+
// uint32_t
77+
BOOST_TEST(next_power_of_two<std::uint32_t>(0u) == 1u);
78+
BOOST_TEST(next_power_of_two<std::uint32_t>(100000u) == 131072u);
79+
BOOST_TEST(next_power_of_two<std::uint32_t>(1u << 30) == 1u << 30);
80+
BOOST_TEST(next_power_of_two<std::uint32_t>((1u << 30) + 1) == 1u << 31);
81+
82+
// uint64_t
83+
BOOST_TEST(next_power_of_two<std::uint64_t>(0u) == 1u);
84+
BOOST_TEST(next_power_of_two<std::uint64_t>(1ull << 40) == 1ull << 40);
85+
BOOST_TEST(next_power_of_two<std::uint64_t>((1ull << 40) + 1) == 1ull << 41);
86+
BOOST_TEST(next_power_of_two<std::uint64_t>(1ull << 62) == 1ull << 62);
87+
BOOST_TEST(next_power_of_two<std::uint64_t>((1ull << 62) + 1) == 1ull << 63);
88+
}
89+
90+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)