From 67c8411c37b483caa2fe3f7f4f40b68ed2a9bcf7 Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Wed, 23 Mar 2022 07:08:23 +0100 Subject: [PATCH 1/3] test: Adds a test for HexStr that checks all 256 bytes This makes sure the whole HexStr mapping table is checked. --- src/test/util_tests.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index b5d8411e1d..269f531a0d 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -198,6 +198,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr) BOOST_CHECK_EQUAL(HexStr(in_s), 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(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) From 4e2b99f72a90b956f3050095abed4949aff9b516 Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Wed, 23 Mar 2022 07:43:58 +0100 Subject: [PATCH 2/3] bench: Adds a benchmark for HexStr Benchmarks conversion of a full binary block into hex, like it is done in rest.cpp. --- src/Makefile.bench.include | 1 + src/bench/strencodings.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/bench/strencodings.cpp diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 5dae4374e3..a6cda582a8 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -44,6 +44,7 @@ bench_bench_bitcoin_SOURCES = \ bench/rollingbloom.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ + bench/strencodings.cpp \ bench/util_time.cpp \ bench/verify_script.cpp diff --git a/src/bench/strencodings.cpp b/src/bench/strencodings.cpp new file mode 100644 index 0000000000..69b3a83cbf --- /dev/null +++ b/src/bench/strencodings.cpp @@ -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 +#include +#include + +static void HexStrBench(benchmark::Bench& bench) +{ + auto const& data = benchmark::data::block413567; + bench.batch(data.size()).unit("byte").run([&] { + auto hex = HexStr(data); + ankerl::nanobench::doNotOptimizeAway(hex); + }); +} + +BENCHMARK(HexStrBench); From 5e61532e72c1021fda9c7b213bd9cf397cb3a802 Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Wed, 23 Mar 2022 07:08:32 +0100 Subject: [PATCH 3/3] util: optimizes HexStr 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 PR 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. --- src/util/strencodings.cpp | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 940fa90da2..84549bf0f1 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -508,17 +509,37 @@ std::string Capitalize(std::string str) return str; } +namespace { + +using ByteAsHex = std::array; + +constexpr std::array CreateByteToHexMap() +{ + constexpr char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + std::array 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 s) { std::string rv(s.size() * 2, '\0'); - static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - auto it = rv.begin(); + static constexpr auto byte_to_hex = CreateByteToHexMap(); + static_assert(sizeof(byte_to_hex) == 512); + + char* it = rv.data(); for (uint8_t v : s) { - *it++ = hexmap[v >> 4]; - *it++ = hexmap[v & 15]; + std::memcpy(it, byte_to_hex[v].data(), 2); + it += 2; } - assert(it == rv.end()); + + assert(it == rv.data() + rv.size()); return rv; }