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 2640582 commit 6b684c4
Showing 1 changed file with 20 additions and 15 deletions.
35 changes: 20 additions & 15 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 nteger division of x by y.
static int ceilDiv(int x, int y) {
Expand All @@ -69,36 +71,39 @@ 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> inputAsLongs;
inputAsLongs.reserve(effectiveLength);
for (; *input && !IsSpace(*input); ++input) {
// Convert the Base58 string to a long 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;
inputAsLongs.push_back(digit);
auto index = (groupOffset + i) / decodingBatch;
inputBatched[index] *= base;
inputBatched[index] += digit;
}
for (; *input; ++input)
if (!IsSpace(*input)) return false;

while (*input && IsSpace(*input)) ++input;
if (*input) return false;
if (!IsSpace(*input)) return false; // Ensure no non-space characters after processing.

auto resultLength{leading};
for (auto i{0U}; i < inputAsLongs.size(); ++resultLength) {
for (auto i{0U}; i < inputBatched.size(); ++resultLength) {
int64_t remainder = 0;
for (auto j{i}; j < inputAsLongs.size(); ++j) {
long accumulator = (remainder * 58) + inputAsLongs[j];
inputAsLongs[j] = accumulator / 256;
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;
}
if (resultLength >= maxRetLen) return false;
result.push_back(remainder);

while (i < inputAsLongs.size() && inputAsLongs[i] == 0)
while (i < inputBatched.size() && inputBatched[i] == 0)
++i; // Skip new leading zeros
}

Expand Down

0 comments on commit 6b684c4

Please sign in to comment.