-
Notifications
You must be signed in to change notification settings - Fork 30.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
src: make base64 decoding 10-15% faster #2193
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
var assert = require('assert'); | ||
var common = require('../common.js'); | ||
|
||
var bench = common.createBenchmark(main, {}); | ||
|
||
function main(conf) { | ||
for (var s = 'abcd'; s.length < 32 << 20; s += s); | ||
s.match(/./); // Flatten string. | ||
assert.equal(s.length % 4, 0); | ||
var b = Buffer(s.length / 4 * 3); | ||
b.write(s, 0, s.length, 'base64'); | ||
bench.start(); | ||
for (var i = 0; i < 32; i += 1) b.base64Write(s, 0, s.length); | ||
bench.end(32); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,7 +132,7 @@ size_t base64_decoded_size(const TypeName* src, size_t size) { | |
|
||
|
||
// supports regular and URL-safe base64 | ||
static const int unbase64_table[] = | ||
static const int8_t unbase64_table[] = | ||
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, | ||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
-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[] = | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 | ||
}; | ||
#define unbase64(x) unbase64_table[(uint8_t)(x)] | ||
#define unbase64(x) \ | ||
static_cast<uint8_t>(unbase64_table[static_cast<uint8_t>(x)]) | ||
|
||
|
||
template <typename TypeName> | ||
size_t base64_decode(char* buf, | ||
size_t len, | ||
const TypeName* src, | ||
const size_t srcLen) { | ||
char a, b, c, d; | ||
char* dst = buf; | ||
char* dstEnd = buf + len; | ||
const TypeName* srcEnd = src + srcLen; | ||
|
||
while (src < srcEnd && dst < dstEnd) { | ||
int remaining = srcEnd - src; | ||
|
||
while (unbase64(*src) < 0 && src < srcEnd) | ||
src++, remaining--; | ||
if (remaining == 0 || *src == '=') | ||
break; | ||
a = unbase64(*src++); | ||
|
||
while (unbase64(*src) < 0 && src < srcEnd) | ||
src++, remaining--; | ||
if (remaining <= 1 || *src == '=') | ||
break; | ||
b = unbase64(*src++); | ||
|
||
*dst++ = (a << 2) | ((b & 0x30) >> 4); | ||
if (dst == dstEnd) | ||
break; | ||
|
||
while (unbase64(*src) < 0 && src < srcEnd) | ||
src++, remaining--; | ||
if (remaining <= 2 || *src == '=') | ||
break; | ||
c = unbase64(*src++); | ||
size_t base64_decode_slow(char* dst, size_t dstlen, | ||
const TypeName* src, size_t srclen) { | ||
uint8_t hi; | ||
uint8_t lo; | ||
size_t i = 0; | ||
size_t k = 0; | ||
for (;;) { | ||
#define V(expr) \ | ||
while (i < srclen) { \ | ||
const uint8_t c = src[i]; \ | ||
lo = unbase64(c); \ | ||
i += 1; \ | ||
if (lo < 64) \ | ||
break; /* Legal character. */ \ | ||
if (c == '=') \ | ||
return k; \ | ||
} \ | ||
expr; \ | ||
if (i >= srclen) \ | ||
return k; \ | ||
if (k >= dstlen) \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style nit: Can this be combined with the previous There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote it like this because personally, I find separate statements easier to read than boolean combinators. With the later, it's just easier to mix up the logic. |
||
return k; \ | ||
hi = lo; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may be totally missing something. But to me, it looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have to admit the codegen macro kind of obscures it but the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah. Understood now. Thanks for clarifying :-) |
||
V(/* Nothing. */); | ||
V(dst[k++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4)); | ||
V(dst[k++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2)); | ||
V(dst[k++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0)); | ||
#undef V | ||
} | ||
UNREACHABLE(); | ||
} | ||
|
||
*dst++ = ((b & 0x0F) << 4) | ((c & 0x3C) >> 2); | ||
if (dst == dstEnd) | ||
break; | ||
|
||
while (unbase64(*src) < 0 && src < srcEnd) | ||
src++, remaining--; | ||
if (remaining <= 3 || *src == '=') | ||
template <typename TypeName> | ||
size_t base64_decode_fast(char* const dst, const size_t dstlen, | ||
const TypeName* const src, const size_t srclen, | ||
const size_t decoded_size) { | ||
const size_t available = dstlen < decoded_size ? dstlen : decoded_size; | ||
const size_t max_i = srclen / 4 * 4; | ||
const size_t max_k = available / 3 * 3; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hope we are doing the integer floor operation in these two statements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you mean? I can't tell if you're making a suggestion or questioning the code. |
||
size_t i = 0; | ||
size_t k = 0; | ||
while (i < max_i && k < max_k) { | ||
const uint32_t v = | ||
unbase64(src[i + 0]) << 24 | | ||
unbase64(src[i + 1]) << 16 | | ||
unbase64(src[i + 2]) << 8 | | ||
unbase64(src[i + 3]); | ||
// If MSB is set, input contains whitespace or is not valid base64. | ||
if (v & 0x80808080) { | ||
break; | ||
d = unbase64(*src++); | ||
|
||
*dst++ = ((c & 0x03) << 6) | (d & 0x3F); | ||
} | ||
dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03); | ||
dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F); | ||
dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F); | ||
i += 4; | ||
k += 3; | ||
} | ||
|
||
return dst - buf; | ||
if (i < srclen && k < dstlen) { | ||
return k + base64_decode_slow(dst + k, dstlen - k, src + i, srclen - i); | ||
} | ||
return k; | ||
} | ||
|
||
|
||
//// HEX //// | ||
template <typename TypeName> | ||
size_t base64_decode(char* const dst, const size_t dstlen, | ||
const TypeName* const src, const size_t srclen) { | ||
const size_t decoded_size = base64_decoded_size(src, srclen); | ||
return base64_decode_fast(dst, dstlen, src, srclen, decoded_size); | ||
} | ||
|
||
|
||
template <typename TypeName> | ||
unsigned hex2bin(TypeName c) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be just me, but defining it outside the loop might improve the readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convention in the source tree is to define V-style codegen macros right before use.