Merge bitcoin/bitcoin#24852: util: optimize HexStr

5e61532e72c1021fda9c7b213bd9cf397cb3a802 util: optimizes HexStr (Martin Leitner-Ankerl)
4e2b99f72a90b956f3050095abed4949aff9b516 bench: Adds a benchmark for HexStr (Martin Leitner-Ankerl)
67c8411c37b483caa2fe3f7f4f40b68ed2a9bcf7 test: Adds a test for HexStr that checks all 256 bytes (Martin Leitner-Ankerl)

Pull request description:

  In my benchmark, this rewrite improves runtime 27% (g++) to 46% (clang++) for the benchmark `HexStrBench`:

  g++ 11.2.0
  |             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
  |                0.94 |    1,061,381,310.36 |    0.7% |           12.00 |            3.01 |  3.990 |           1.00 |    0.0% |      0.01 | `HexStrBench` master
  |                0.68 |    1,465,366,544.25 |    1.7% |            6.00 |            2.16 |  2.778 |           1.00 |    0.0% |      0.01 | `HexStrBench` branch

  clang++ 13.0.1
  |             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
  |                0.80 |    1,244,713,415.92 |    0.9% |           10.00 |            2.56 |  3.913 |           0.50 |    0.0% |      0.01 | `HexStrBench` master
  |                0.43 |    2,324,188,940.72 |    0.2% |            4.00 |            1.37 |  2.914 |           0.25 |    0.0% |      0.01 | `HexStrBench` branch

  Note that the idea for this change comes from denis2342 in #23364. This is a rewrite so no unaligned accesses occur.

  Also, the lookup table is now calculated at compile time, which hopefully makes the code a bit easier to review.

ACKs for top commit:
  laanwj:
    Code review ACK 5e61532e72c1021fda9c7b213bd9cf397cb3a802
  aureleoules:
    tACK 5e61532e72c1021fda9c7b213bd9cf397cb3a802.
  theStack:
    ACK 5e61532e72c1021fda9c7b213bd9cf397cb3a802 🚤

Tree-SHA512: 40b53d5908332473ef24918d3a80ad1292b60566c02585fa548eb4c3189754971be5a70325f4968fce6d714df898b52d9357aba14d4753a8c70e6ffd273a2319
This commit is contained in:
laanwj 2022-05-04 20:19:51 +02:00 committed by pasta
parent 1288494d4a
commit e4f8b7097d
No known key found for this signature in database
GPG Key ID: E2F3D7916E722D38
4 changed files with 64 additions and 6 deletions

View File

@ -44,6 +44,7 @@ bench_bench_dash_SOURCES = \
bench/pool.cpp \ bench/pool.cpp \
bench/rpc_blockchain.cpp \ bench/rpc_blockchain.cpp \
bench/rpc_mempool.cpp \ bench/rpc_mempool.cpp \
bench/strencodings.cpp \
bench/util_time.cpp \ bench/util_time.cpp \
bench/base58.cpp \ bench/base58.cpp \
bench/bech32.cpp \ bench/bech32.cpp \

View File

@ -0,0 +1,18 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bench/bench.h>
#include <bench/data.h>
#include <util/strencodings.h>
static void HexStrBench(benchmark::Bench& bench)
{
auto const& data = benchmark::data::block813851;
bench.batch(data.size()).unit("byte").run([&] {
auto hex = HexStr(data);
ankerl::nanobench::doNotOptimizeAway(hex);
});
}
BENCHMARK(HexStrBench);

View File

@ -206,6 +206,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr)
BOOST_CHECK_EQUAL(HexStr(in_s), out_exp); BOOST_CHECK_EQUAL(HexStr(in_s), out_exp);
BOOST_CHECK_EQUAL(HexStr(in_b), out_exp); BOOST_CHECK_EQUAL(HexStr(in_b), out_exp);
} }
{
auto input = std::string();
for (size_t i=0; i<256; ++i) {
input.push_back(static_cast<char>(i));
}
auto hex = HexStr(input);
BOOST_TEST_REQUIRE(hex.size() == 512);
static constexpr auto hexmap = std::string_view("0123456789abcdef");
for (size_t i = 0; i < 256; ++i) {
auto upper = hexmap.find(hex[i * 2]);
auto lower = hexmap.find(hex[i * 2 + 1]);
BOOST_TEST_REQUIRE(upper != std::string_view::npos);
BOOST_TEST_REQUIRE(lower != std::string_view::npos);
BOOST_TEST_REQUIRE(i == upper*16 + lower);
}
}
} }
BOOST_AUTO_TEST_CASE(span_write_bytes) BOOST_AUTO_TEST_CASE(span_write_bytes)

View File

@ -9,6 +9,7 @@
#include <tinyformat.h> #include <tinyformat.h>
#include <algorithm> #include <algorithm>
#include <array>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <limits> #include <limits>
@ -493,17 +494,37 @@ std::string Capitalize(std::string str)
return str; return str;
} }
namespace {
using ByteAsHex = std::array<char, 2>;
constexpr std::array<ByteAsHex, 256> CreateByteToHexMap()
{
constexpr char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
std::array<ByteAsHex, 256> byte_to_hex{};
for (size_t i = 0; i < byte_to_hex.size(); ++i) {
byte_to_hex[i][0] = hexmap[i >> 4];
byte_to_hex[i][1] = hexmap[i & 15];
}
return byte_to_hex;
}
} // namespace
std::string HexStr(const Span<const uint8_t> s) std::string HexStr(const Span<const uint8_t> s)
{ {
std::string rv(s.size() * 2, '\0'); std::string rv(s.size() * 2, '\0');
static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', static constexpr auto byte_to_hex = CreateByteToHexMap();
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; static_assert(sizeof(byte_to_hex) == 512);
auto it = rv.begin();
char* it = rv.data();
for (uint8_t v : s) { for (uint8_t v : s) {
*it++ = hexmap[v >> 4]; std::memcpy(it, byte_to_hex[v].data(), 2);
*it++ = hexmap[v & 15]; it += 2;
} }
assert(it == rv.end());
assert(it == rv.data() + rv.size());
return rv; return rv;
} }