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 3c5435b
Showing 1 changed file with 17 additions and 9 deletions.
26 changes: 17 additions & 9 deletions src/base58.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ 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) {
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 Down

0 comments on commit 3c5435b

Please sign in to comment.