Skip to content

Commit

Permalink
Speed up Base58 decoding with 64-bit packing
Browse files Browse the repository at this point in the history
> make && ./src/bench/bench_bitcoin --filter=Base58Decode --min-time=1000

After:
```
|             ns/byte |              byte/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|                8.79 |      113,767,685.06 |    0.1% |      1.10 | `Base58Decode`
|                8.78 |      113,831,528.33 |    0.0% |      1.10 | `Base58Decode`
|                8.80 |      113,607,470.35 |    0.2% |      1.10 | `Base58Decode`
```
  • Loading branch information
Lőrinc committed Mar 7, 2024
1 parent 81be9d6 commit 947ea98
Showing 1 changed file with 21 additions and 13 deletions.
34 changes: 21 additions & 13 deletions src/base58.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ static constexpr int log256_58Ratio = 1366; // Approximation of log(256)/log(bas

// Defines the size of groups that fit into 64 bit batches, processed together for encoding and decoding efficiency.
static constexpr int encodingBatch = 7;
static constexpr int decodingBatch = 9;
static constexpr int64_t decodingPowerOf58 = (int64_t) base*base*base*base*base*base*base*base*base; // pow(base, decodingBatch)

// The ceiling integer division of x by y.
static int ceilDiv(int x, int y) {
static int CeilDiv(int x, int y) {
return (x + (y - 1)) / y;
}

// The floor modulus of x by y, adjusting for negative values.
static int floorMod(int x, int y) {
static int FloorMod(int x, int y) {
auto r = x % y;
return r < 0 ? r + y : r;
}
Expand All @@ -69,26 +71,32 @@ static int floorMod(int x, int y) {
for (; *input == '1'; ++input, ++leading)
if (leading >= maxRetLen) return false;

auto effectiveLength = strlen(input);
int size = 1 + effectiveLength * log58_256Ratio / baseScale;
auto effectiveLength{0};
for (auto p{input}; *p; ++p)
if (!IsSpace(*p)) ++effectiveLength;

auto size = 1 + effectiveLength * log58_256Ratio / baseScale;
result.reserve(leading + size);
result.assign(leading, 0x00);

std::vector<uint8_t> inputBatched;
inputBatched.reserve(effectiveLength);
for (; *input && !IsSpace(*input); ++input) {
// Convert the Base58 string to a 64 bit representation for faster manipulation.
std::vector<int64_t> inputBatched(CeilDiv(effectiveLength, decodingBatch), 0);
auto groupOffset = FloorMod(-effectiveLength, decodingBatch);
for (auto i{0U}; *input && !IsSpace(*input); ++input, ++i) {
auto digit = mapBase58[static_cast<uint8_t>(*input)];
if (digit == -1) return false;
inputBatched.push_back(digit);
auto index = (groupOffset + i) / decodingBatch;
inputBatched[index] *= base;
inputBatched[index] += digit;
}
for (; *input; ++input)
if (!IsSpace(*input)) return false;
if (!IsSpace(*input)) return false; // Ensure no non-space characters after processing.

auto resultLength{leading};
for (auto i{0U}; i < inputBatched.size(); ++resultLength) {
int64_t remainder = 0;
for (auto j{i}; j < inputBatched.size(); ++j) {
auto accumulator = (remainder * 58) + inputBatched[j];
for (auto j{i}; j < inputBatched.size(); ++j) { // Calculate next digit, dividing inputBatched
auto accumulator = (remainder * decodingPowerOf58) + inputBatched[j];
inputBatched[j] = accumulator / 256;
remainder = accumulator % 256;
}
Expand All @@ -106,8 +114,8 @@ static int floorMod(int x, int y) {

auto BatchInput(const Span<const unsigned char>& input, int start) -> std::vector<int64_t> {
int effectiveLength = input.size() - start;
std::vector<int64_t> inputBatched(ceilDiv(effectiveLength, encodingBatch), 0);
int groupOffset = floorMod(-effectiveLength, encodingBatch) - start;
std::vector<int64_t> inputBatched(CeilDiv(effectiveLength, encodingBatch), 0);
int groupOffset = FloorMod(-effectiveLength, encodingBatch) - start;

for (uint32_t i = start; i < input.size(); ++i) {
int index = (groupOffset + static_cast<int>(i)) / encodingBatch;
Expand Down

0 comments on commit 947ea98

Please sign in to comment.