authorgravatar for joad.nacer@gmail.comjoadnacer <joad.nacer@gmail.com> 2023-10-13 00:38:55+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-14 23:28:19-07:00
loga241cf90d66ee0a47fafc49dc65fe32871557a01
tree47f682f0bb7fff82ffea4ebcffdb902b46ff4d25
parentb0f031f5730be496872591dce3242004cf9a6f22

std.base64: Improve Encoder/Decoder performance


1 files changed, 86 insertions(+), 5 deletions(-)

lib/std/base64.zig+86-5
...@@ -102,14 +102,28 @@ pub const Base64Encoder = struct {...@@ -102,14 +102,28 @@ pub const Base64Encoder = struct {
102102
103 var idx: usize = 0;103 var idx: usize = 0;
104 var out_idx: usize = 0;104 var out_idx: usize = 0;
105 while (idx + 2 < source.len) : (idx += 3) {105 while (idx + 15 < source.len) : (idx += 12) {
106 const bits = std.mem.readIntBig(u128, source[idx..][0..16]);
107 inline for (0..16) |i| {
108 dest[out_idx + i] = encoder.alphabet_chars[@truncate((bits >> (122 - i * 6)) & 0x3f)];
109 }
110 out_idx += 16;
111 }
112 while (idx + 3 < source.len) : (idx += 3) {
113 const bits = std.mem.readIntBig(u32, source[idx..][0..4]);
114 dest[out_idx] = encoder.alphabet_chars[(bits >> 26) & 0x3f];
115 dest[out_idx + 1] = encoder.alphabet_chars[(bits >> 20) & 0x3f];
116 dest[out_idx + 2] = encoder.alphabet_chars[(bits >> 14) & 0x3f];
117 dest[out_idx + 3] = encoder.alphabet_chars[(bits >> 8) & 0x3f];
118 out_idx += 4;
119 }
120 if (idx + 2 < source.len) {
106 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2];121 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2];
107 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)];122 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)];
108 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2 | (source[idx + 2] >> 6)];123 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2 | (source[idx + 2] >> 6)];
109 dest[out_idx + 3] = encoder.alphabet_chars[source[idx + 2] & 0x3f];124 dest[out_idx + 3] = encoder.alphabet_chars[source[idx + 2] & 0x3f];
110 out_idx += 4;125 out_idx += 4;
111 }126 } else if (idx + 1 < source.len) {
112 if (idx + 1 < source.len) {
113 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2];127 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2];
114 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)];128 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)];
115 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2];129 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2];
...@@ -130,15 +144,18 @@ pub const Base64Encoder = struct {...@@ -130,15 +144,18 @@ pub const Base64Encoder = struct {
130144
131pub const Base64Decoder = struct {145pub const Base64Decoder = struct {
132 const invalid_char: u8 = 0xff;146 const invalid_char: u8 = 0xff;
147 const invalid_char_tst: u32 = 0xff000000;
133148
134 /// e.g. 'A' => 0.149 /// e.g. 'A' => 0.
135 /// `invalid_char` for any value not in the 64 alphabet chars.150 /// `invalid_char` for any value not in the 64 alphabet chars.
136 char_to_index: [256]u8,151 char_to_index: [256]u8,
152 fast_char_to_index: [4][256]u32,
137 pad_char: ?u8,153 pad_char: ?u8,
138154
139 pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder {155 pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder {
140 var result = Base64Decoder{156 var result = Base64Decoder{
141 .char_to_index = [_]u8{invalid_char} ** 256,157 .char_to_index = [_]u8{invalid_char} ** 256,
158 .fast_char_to_index = .{[_]u32{invalid_char_tst} ** 256} ** 4,
142 .pad_char = pad_char,159 .pad_char = pad_char,
143 };160 };
144161
...@@ -147,6 +164,12 @@ pub const Base64Decoder = struct {...@@ -147,6 +164,12 @@ pub const Base64Decoder = struct {
147 assert(!char_in_alphabet[c]);164 assert(!char_in_alphabet[c]);
148 assert(pad_char == null or c != pad_char.?);165 assert(pad_char == null or c != pad_char.?);
149166
167 const ci = @as(u32, @intCast(i));
168 result.fast_char_to_index[0][c] = ci << 2;
169 result.fast_char_to_index[1][c] = (ci >> 4) | ((ci & 0x0f) << 12);
170 result.fast_char_to_index[2][c] = ((ci & 0x3) << 22) | ((ci & 0x3c) << 6);
171 result.fast_char_to_index[3][c] = ci << 16;
172
150 result.char_to_index[c] = @as(u8, @intCast(i));173 result.char_to_index[c] = @as(u8, @intCast(i));
151 char_in_alphabet[c] = true;174 char_in_alphabet[c] = true;
152 }175 }
...@@ -184,11 +207,39 @@ pub const Base64Decoder = struct {...@@ -184,11 +207,39 @@ pub const Base64Decoder = struct {
184 /// invalid padding results in error.InvalidPadding.207 /// invalid padding results in error.InvalidPadding.
185 pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void {208 pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void {
186 if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding;209 if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding;
210 var dest_idx: usize = 0;
211 var fast_src_idx: usize = 0;
187 var acc: u12 = 0;212 var acc: u12 = 0;
188 var acc_len: u4 = 0;213 var acc_len: u4 = 0;
189 var dest_idx: usize = 0;
190 var leftover_idx: ?usize = null;214 var leftover_idx: ?usize = null;
191 for (source, 0..) |c, src_idx| {215 while (fast_src_idx + 16 < source.len and dest_idx + 15 < dest.len) : ({
216 fast_src_idx += 16;
217 dest_idx += 12;
218 }) {
219 var bits: u128 = 0;
220 inline for (0..4) |i| {
221 var new_bits: u128 = decoder.fast_char_to_index[0][source[fast_src_idx + i * 4]];
222 new_bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1 + i * 4]];
223 new_bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2 + i * 4]];
224 new_bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3 + i * 4]];
225 if ((new_bits & invalid_char_tst) != 0) return error.InvalidCharacter;
226 bits |= (new_bits << (24 * i));
227 }
228 std.mem.writeIntLittle(u128, dest[dest_idx..][0..16], bits);
229 }
230 while (fast_src_idx + 4 < source.len and dest_idx + 3 < dest.len) : ({
231 fast_src_idx += 4;
232 dest_idx += 3;
233 }) {
234 var bits = decoder.fast_char_to_index[0][source[fast_src_idx]];
235 bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1]];
236 bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2]];
237 bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3]];
238 if ((bits & invalid_char_tst) != 0) return error.InvalidCharacter;
239 std.mem.writeIntLittle(u32, dest[dest_idx..][0..4], bits);
240 }
241 var remaining = source[fast_src_idx..];
242 for (remaining, fast_src_idx..) |c, src_idx| {
192 const d = decoder.char_to_index[c];243 const d = decoder.char_to_index[c];
193 if (d == invalid_char) {244 if (d == invalid_char) {
194 if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter;245 if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter;
...@@ -338,6 +389,10 @@ fn testBase64() !void {...@@ -338,6 +389,10 @@ fn testBase64() !void {
338 try testAllApis(codecs, "foob", "Zm9vYg==");389 try testAllApis(codecs, "foob", "Zm9vYg==");
339 try testAllApis(codecs, "fooba", "Zm9vYmE=");390 try testAllApis(codecs, "fooba", "Zm9vYmE=");
340 try testAllApis(codecs, "foobar", "Zm9vYmFy");391 try testAllApis(codecs, "foobar", "Zm9vYmFy");
392 try testAllApis(codecs, "foobarfoobarfoo", "Zm9vYmFyZm9vYmFyZm9v");
393 try testAllApis(codecs, "foobarfoobarfoob", "Zm9vYmFyZm9vYmFyZm9vYg==");
394 try testAllApis(codecs, "foobarfoobarfooba", "Zm9vYmFyZm9vYmFyZm9vYmE=");
395 try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy");
341396
342 try testDecodeIgnoreSpace(codecs, "", " ");397 try testDecodeIgnoreSpace(codecs, "", " ");
343 try testDecodeIgnoreSpace(codecs, "f", "Z g= =");398 try testDecodeIgnoreSpace(codecs, "f", "Z g= =");
...@@ -357,11 +412,23 @@ fn testBase64() !void {...@@ -357,11 +412,23 @@ fn testBase64() !void {
357 try testError(codecs, "A/==", error.InvalidPadding);412 try testError(codecs, "A/==", error.InvalidPadding);
358 try testError(codecs, "A===", error.InvalidPadding);413 try testError(codecs, "A===", error.InvalidPadding);
359 try testError(codecs, "====", error.InvalidPadding);414 try testError(codecs, "====", error.InvalidPadding);
415 try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter);
416 try testError(codecs, "Zm9vYmFyZm9vYmFyAA=A", error.InvalidPadding);
417 try testError(codecs, "Zm9vYmFyZm9vYmFyAA/=", error.InvalidPadding);
418 try testError(codecs, "Zm9vYmFyZm9vYmFyA/==", error.InvalidPadding);
419 try testError(codecs, "Zm9vYmFyZm9vYmFyA===", error.InvalidPadding);
420 try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter);
421 try testError(codecs, "Zm9vYmFyZm9vAA=A", error.InvalidPadding);
422 try testError(codecs, "Zm9vYmFyZm9vAA/=", error.InvalidPadding);
423 try testError(codecs, "Zm9vYmFyZm9vA/==", error.InvalidPadding);
424 try testError(codecs, "Zm9vYmFyZm9vA===", error.InvalidPadding);
360425
361 try testNoSpaceLeftError(codecs, "AA==");426 try testNoSpaceLeftError(codecs, "AA==");
362 try testNoSpaceLeftError(codecs, "AAA=");427 try testNoSpaceLeftError(codecs, "AAA=");
363 try testNoSpaceLeftError(codecs, "AAAA");428 try testNoSpaceLeftError(codecs, "AAAA");
364 try testNoSpaceLeftError(codecs, "AAAAAA==");429 try testNoSpaceLeftError(codecs, "AAAAAA==");
430
431 try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA");
365}432}
366433
367fn testBase64UrlSafeNoPad() !void {434fn testBase64UrlSafeNoPad() !void {
...@@ -374,6 +441,7 @@ fn testBase64UrlSafeNoPad() !void {...@@ -374,6 +441,7 @@ fn testBase64UrlSafeNoPad() !void {
374 try testAllApis(codecs, "foob", "Zm9vYg");441 try testAllApis(codecs, "foob", "Zm9vYg");
375 try testAllApis(codecs, "fooba", "Zm9vYmE");442 try testAllApis(codecs, "fooba", "Zm9vYmE");
376 try testAllApis(codecs, "foobar", "Zm9vYmFy");443 try testAllApis(codecs, "foobar", "Zm9vYmFy");
444 try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy");
377445
378 try testDecodeIgnoreSpace(codecs, "", " ");446 try testDecodeIgnoreSpace(codecs, "", " ");
379 try testDecodeIgnoreSpace(codecs, "f", "Z g ");447 try testDecodeIgnoreSpace(codecs, "f", "Z g ");
...@@ -392,11 +460,15 @@ fn testBase64UrlSafeNoPad() !void {...@@ -392,11 +460,15 @@ fn testBase64UrlSafeNoPad() !void {
392 try testError(codecs, "A/==", error.InvalidCharacter);460 try testError(codecs, "A/==", error.InvalidCharacter);
393 try testError(codecs, "A===", error.InvalidCharacter);461 try testError(codecs, "A===", error.InvalidCharacter);
394 try testError(codecs, "====", error.InvalidCharacter);462 try testError(codecs, "====", error.InvalidCharacter);
463 try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter);
464 try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter);
395465
396 try testNoSpaceLeftError(codecs, "AA");466 try testNoSpaceLeftError(codecs, "AA");
397 try testNoSpaceLeftError(codecs, "AAA");467 try testNoSpaceLeftError(codecs, "AAA");
398 try testNoSpaceLeftError(codecs, "AAAA");468 try testNoSpaceLeftError(codecs, "AAAA");
399 try testNoSpaceLeftError(codecs, "AAAAAA");469 try testNoSpaceLeftError(codecs, "AAAAAA");
470
471 try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA");
400}472}
401473
402fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void {474fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void {
...@@ -457,3 +529,12 @@ fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void {...@@ -457,3 +529,12 @@ fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void {
457 return error.ExpectedError;529 return error.ExpectedError;
458 } else |err| if (err != error.NoSpaceLeft) return err;530 } else |err| if (err != error.NoSpaceLeft) return err;
459}531}
532
533fn testFourBytesDestNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void {
534 const decoder_ignore_space = codecs.decoderWithIgnore(" ");
535 var buffer: [0x100]u8 = undefined;
536 var decoded = buffer[0..4];
537 if (decoder_ignore_space.decode(decoded, encoded)) |_| {
538 return error.ExpectedError;
539 } else |err| if (err != error.NoSpaceLeft) return err;
540}