Skip to content

Commit 8fd3ce1

Browse files
committed
src: make base64 decoding 50% faster
Make the inner loop execute fewer compare-and-branch executions per processed byte, resulting in a 50% or more speedup. This coincidentally fixes an out-of-bounds read: while (unbase64(*src) < 0 && src < srcEnd) Should have read: while (src < srcEnd && unbase64(*src) < 0) But this commit removes the offending code altogether. Fixes: #2166 PR-URL: #2193 Reviewed-By: Trevor Norris <[email protected]>
1 parent b148c0d commit 8fd3ce1

File tree

2 files changed

+83
-47
lines changed

2 files changed

+83
-47
lines changed
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
var assert = require('assert');
2+
var common = require('../common.js');
3+
4+
var bench = common.createBenchmark(main, {});
5+
6+
function main(conf) {
7+
for (var s = 'abcd'; s.length < 32 << 20; s += s);
8+
s.match(/./); // Flatten string.
9+
assert.equal(s.length % 4, 0);
10+
var b = Buffer(s.length / 4 * 3);
11+
b.write(s, 0, s.length, 'base64');
12+
bench.start();
13+
for (var i = 0; i < 32; i += 1) b.base64Write(s, 0, s.length);
14+
bench.end(32);
15+
}

src/string_bytes.cc

+68-47
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ size_t base64_decoded_size(const TypeName* src, size_t size) {
132132

133133

134134
// supports regular and URL-safe base64
135-
static const int unbase64_table[] =
135+
static const int8_t unbase64_table[] =
136136
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1,
137137
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
138138
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63,
@@ -150,62 +150,83 @@ static const int unbase64_table[] =
150150
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
151151
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
152152
};
153-
#define unbase64(x) unbase64_table[(uint8_t)(x)]
153+
#define unbase64(x) \
154+
static_cast<uint8_t>(unbase64_table[static_cast<uint8_t>(x)])
154155

155156

156157
template <typename TypeName>
157-
size_t base64_decode(char* buf,
158-
size_t len,
159-
const TypeName* src,
160-
const size_t srcLen) {
161-
char a, b, c, d;
162-
char* dst = buf;
163-
char* dstEnd = buf + len;
164-
const TypeName* srcEnd = src + srcLen;
165-
166-
while (src < srcEnd && dst < dstEnd) {
167-
int remaining = srcEnd - src;
168-
169-
while (unbase64(*src) < 0 && src < srcEnd)
170-
src++, remaining--;
171-
if (remaining == 0 || *src == '=')
172-
break;
173-
a = unbase64(*src++);
174-
175-
while (unbase64(*src) < 0 && src < srcEnd)
176-
src++, remaining--;
177-
if (remaining <= 1 || *src == '=')
178-
break;
179-
b = unbase64(*src++);
180-
181-
*dst++ = (a << 2) | ((b & 0x30) >> 4);
182-
if (dst == dstEnd)
183-
break;
184-
185-
while (unbase64(*src) < 0 && src < srcEnd)
186-
src++, remaining--;
187-
if (remaining <= 2 || *src == '=')
188-
break;
189-
c = unbase64(*src++);
158+
size_t base64_decode_slow(char* dst, size_t dstlen,
159+
const TypeName* src, size_t srclen) {
160+
uint8_t hi;
161+
uint8_t lo;
162+
size_t i = 0;
163+
size_t k = 0;
164+
for (;;) {
165+
#define V(expr) \
166+
while (i < srclen) { \
167+
const uint8_t c = src[i]; \
168+
lo = unbase64(c); \
169+
i += 1; \
170+
if (lo < 64) \
171+
break; /* Legal character. */ \
172+
if (c == '=') \
173+
return k; \
174+
} \
175+
expr; \
176+
if (i >= srclen) \
177+
return k; \
178+
if (k >= dstlen) \
179+
return k; \
180+
hi = lo;
181+
V(/* Nothing. */);
182+
V(dst[k++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4));
183+
V(dst[k++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2));
184+
V(dst[k++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0));
185+
#undef V
186+
}
187+
UNREACHABLE();
188+
}
190189

191-
*dst++ = ((b & 0x0F) << 4) | ((c & 0x3C) >> 2);
192-
if (dst == dstEnd)
193-
break;
194190

195-
while (unbase64(*src) < 0 && src < srcEnd)
196-
src++, remaining--;
197-
if (remaining <= 3 || *src == '=')
191+
template <typename TypeName>
192+
size_t base64_decode_fast(char* const dst, const size_t dstlen,
193+
const TypeName* const src, const size_t srclen,
194+
const size_t decoded_size) {
195+
const size_t available = dstlen < decoded_size ? dstlen : decoded_size;
196+
const size_t max_i = srclen / 4 * 4;
197+
const size_t max_k = available / 3 * 3;
198+
size_t i = 0;
199+
size_t k = 0;
200+
while (i < max_i && k < max_k) {
201+
const uint32_t v =
202+
unbase64(src[i + 0]) << 24 |
203+
unbase64(src[i + 1]) << 16 |
204+
unbase64(src[i + 2]) << 8 |
205+
unbase64(src[i + 3]);
206+
// If MSB is set, input contains whitespace or is not valid base64.
207+
if (v & 0x80808080) {
198208
break;
199-
d = unbase64(*src++);
200-
201-
*dst++ = ((c & 0x03) << 6) | (d & 0x3F);
209+
}
210+
dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03);
211+
dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F);
212+
dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F);
213+
i += 4;
214+
k += 3;
202215
}
203-
204-
return dst - buf;
216+
if (i < srclen && k < dstlen) {
217+
return k + base64_decode_slow(dst + k, dstlen - k, src + i, srclen - i);
218+
}
219+
return k;
205220
}
206221

207222

208-
//// HEX ////
223+
template <typename TypeName>
224+
size_t base64_decode(char* const dst, const size_t dstlen,
225+
const TypeName* const src, const size_t srclen) {
226+
const size_t decoded_size = base64_decoded_size(src, srclen);
227+
return base64_decode_fast(dst, dstlen, src, srclen, decoded_size);
228+
}
229+
209230

210231
template <typename TypeName>
211232
unsigned hex2bin(TypeName c) {

0 commit comments

Comments
 (0)