Skip to content

Commit

Permalink
perf: Improve performance of ipv4 address formatting
Browse files Browse the repository at this point in the history
SDB-8486
  • Loading branch information
bnbajwa committed Jan 16, 2025
1 parent de279c3 commit 8ed08a4
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 5 deletions.
3 changes: 3 additions & 0 deletions bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ target_link_libraries(tb-log-bench ${tb_bm_LIBRARY})
add_executable(tb-map-bench Map.bm.cpp)
target_link_libraries(tb-map-bench ${tb_bm_LIBRARY})

add_executable(tb-net-bench Net.bm.cpp)
target_link_libraries(tb-net-bench ${tb_bm_LIBRARY})

add_executable(tb-time-bench Time.bm.cpp)
target_link_libraries(tb-time-bench ${tb_bm_LIBRARY})

Expand Down
97 changes: 97 additions & 0 deletions bench/Net.bm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// The Reactive C++ Toolbox.
// Copyright (C) 2025 Reactive Markets Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <toolbox/net/Endpoint.hpp>
#include <toolbox/util/Random.hpp>
#include <toolbox/util/Stream.hpp>
#include <toolbox/bm.hpp>

#include <algorithm>
#include <cstddef>
#include <vector>
#include <limits>

#include <netinet/in.h>

TOOLBOX_BENCHMARK_MAIN

using namespace std;
using namespace toolbox;

namespace {

template<size_t N>
OStream<N>& make_stream() {
static OStream<N> stream{nullptr};

auto storage = OStream<N>::make_storage();

// make sure all allocated memory is backed by physical pages,
// so that no page faults occur during our benchmarks.
unsigned char* p = static_cast<unsigned char*>(storage.get());
std::fill(p, p+N, 0);

stream.set_storage(std::move(storage));
return stream;
}

std::vector<sockaddr_in> generate_random_ipv4_addresses(size_t N) {
std::vector<sockaddr_in> ret;
ret.reserve(N);

for (size_t i = 0; i < N; i++) {
sockaddr_in s;
s.sin_family = AF_INET;
s.sin_port = htons(randint<in_port_t>(0, std::numeric_limits<in_port_t>::max()));
s.sin_addr = in_addr{randint<uint32_t>(0, std::numeric_limits<uint32_t>::max())};
ret.push_back(s);
}

return ret;
}

OStream<32768>& os = make_stream<32768>();
std::vector<sockaddr_in> rand_ipv4_addresses = generate_random_ipv4_addresses(1024);

ostream& write_ipv4_libc(const sockaddr_in& sa)
{
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &toolbox::remove_const(sa).sin_addr, buf, sizeof(buf));
return os << buf << ':' << ntohs(sa.sin_port);
}

TOOLBOX_BENCHMARK(format_ipv4_endpoint_libc)
{
os.reset();
while (ctx) {
for (auto i : ctx.range(rand_ipv4_addresses.size())) {
auto ip_addr = rand_ipv4_addresses[i];
write_ipv4_libc(ip_addr);
}
}
}

TOOLBOX_BENCHMARK(format_ipv4_endpoint)
{
os.reset();
while (ctx) {
for (auto i : ctx.range(rand_ipv4_addresses.size())) {
auto ip_addr = rand_ipv4_addresses[i];
os << ip_addr;
}
}
}

} // namespace
38 changes: 35 additions & 3 deletions toolbox/net/Endpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,41 @@ ostream& operator<<(ostream& os, const StreamEndpoint& ep)

ostream& operator<<(ostream& os, const sockaddr_in& sa)
{
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &toolbox::remove_const(sa).sin_addr, buf, sizeof(buf));
return os << buf << ':' << ntohs(sa.sin_port);
// biggest possible str: 255.255.255.255:
char buf[16];
char* p = buf;

auto write_u8 = [&p](std::uint8_t v) {
char rd = '0' + (v % 10u);
if (v >= 100u) {
char ld = '0' + ((v / 100u) % 10u);
char md = '0' + ((v / 10u) % 10u);
*p++ = ld;
*p++ = md;
*p++ = rd;
} else if (v >= 10u) {
char ld = '0' + ((v / 10u) % 10u);
*p++ = ld;
*p++ = rd;
} else {
*p++ = rd;
}
};

// ip address in sockaddr_in is in network order (i.e. big endian)
uint32_t addr = sa.sin_addr.s_addr;
auto* ipv4 = std::bit_cast<unsigned char*>(&addr);

write_u8(ipv4[0]);
*p++ = '.';
write_u8(ipv4[1]);
*p++ = '.';
write_u8(ipv4[2]);
*p++ = '.';
write_u8(ipv4[3]);
*p++ = ':';

return os << std::string_view(buf, p) << ntohs(sa.sin_port);
}

ostream& operator<<(ostream& os, const sockaddr_in6& sa)
Expand Down
59 changes: 59 additions & 0 deletions toolbox/net/Endpoint.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,43 @@
#include "Endpoint.hpp"

#include <toolbox/util/String.hpp>
#include <toolbox/util/Random.hpp>
#include <toolbox/util/Stream.hpp>

#include <boost/test/unit_test.hpp>

#include <vector>

using namespace std;
using namespace toolbox;

namespace {
std::vector<sockaddr_in> generate_random_ipv4_addresses(size_t N) {
std::vector<sockaddr_in> ret;
ret.reserve(N);

for (size_t i = 0; i < N; i++) {
sockaddr_in s;
s.sin_family = AF_INET;
s.sin_port = htons(randint<in_port_t>(0, std::numeric_limits<in_port_t>::max()));
s.sin_addr = in_addr{randint<uint32_t>(0, std::numeric_limits<uint32_t>::max())};
ret.push_back(s);
}

return ret;
}

ostream& write_ipv4_libc(ostream& os, const sockaddr_in& sa)
{
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &toolbox::remove_const(sa).sin_addr, buf, sizeof(buf));
return os << buf << ':' << ntohs(sa.sin_port);
}

util::OStream<32> ipv4_os{nullptr};

} // namespace

BOOST_AUTO_TEST_SUITE(EndpointSuite)

BOOST_AUTO_TEST_CASE(ParseDgramUnspec4Case)
Expand Down Expand Up @@ -154,4 +185,32 @@ BOOST_AUTO_TEST_CASE(ParseStreamBindCase)
BOOST_CHECK_EQUAL(to_string(ep), "tcp4://0.0.0.0:80");
}

BOOST_AUTO_TEST_CASE(IPv4Formatting)
{
ipv4_os.set_storage(ipv4_os.make_storage());

const auto rand_ips = generate_random_ipv4_addresses(131'072);
for (const auto& ip : rand_ips) {
ipv4_os.reset();
ipv4_os << ip;
std::string our_str {ipv4_os.data(), ipv4_os.size()};

ipv4_os.reset();
write_ipv4_libc(ipv4_os, ip);
std::string libc_str {ipv4_os.data(), ipv4_os.size()};

// min size = 9
// 0.0.0.0:0

// max size = 21
// 255.255.255.255:65535

BOOST_CHECK(our_str.size() >= 9);
BOOST_CHECK(libc_str.size() >= 9);
BOOST_CHECK(our_str.size() <= 21);
BOOST_CHECK(libc_str.size() <= 21);
BOOST_CHECK_EQUAL(our_str, libc_str);
}
}

BOOST_AUTO_TEST_SUITE_END()
6 changes: 4 additions & 2 deletions toolbox/util/Stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class StreamBuf final : public std::streambuf {
StreamBuf()
: storage_{make_storage<MaxN>}
{
setp(storage_->begin(), storage_->end());
char* begin = static_cast<char*>(storage_.get());
setp(begin, begin + MaxN);
}
~StreamBuf() override = default;

Expand Down Expand Up @@ -74,7 +75,8 @@ class StreamBuf final : public std::streambuf {
void reset() noexcept
{
if (storage_) {
setp(storage_->begin(), storage_->end());
char* begin = static_cast<char*>(storage_.get());
setp(begin, begin + MaxN);
}
}

Expand Down

0 comments on commit 8ed08a4

Please sign in to comment.