Skip to content

Commit ff2eee0

Browse files
committed
perf: improve fromBase64 with u16
1 parent 9a37d21 commit ff2eee0

File tree

1 file changed

+51
-10
lines changed

1 file changed

+51
-10
lines changed

fallback/base64.js

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ export function toBase64(arr, isURL, padding) {
125125

126126
// TODO: can this be optimized? This only affects non-Hermes barebone engines though
127127
const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
128+
const _AA = 0x4141 // 'AA' string in hex, the only allowed char pair to generate 12 zero bits
129+
const _zz = 0x7a7a // 'zz' string in hex, max allowed char pair
128130

129131
export function fromBase64(str, isURL) {
130132
let inputLength = str.length
@@ -152,21 +154,60 @@ export function fromBase64(str, isURL) {
152154
let i = 0
153155

154156
if (nativeEncoder) {
157+
if (!helpers.fromMap16) {
158+
helpers.fromMap16 = new Uint16Array(_zz + 1) // Warning: 64 KiB
159+
const u8 = new Uint8Array(2)
160+
const u16 = new Uint16Array(u8.buffer, u8.byteOffset, 1) // for endianess-agnostic transform
161+
alphabet.forEach((c0, i0) => {
162+
u8[0] = c0.charCodeAt(0) // FIXME, we should avoid calling charCodeAt in a loop
163+
alphabet.forEach((c1, i1) => {
164+
u8[1] = c1.charCodeAt(0)
165+
helpers.fromMap16[u16[0]] = (i0 << 6) | i1
166+
})
167+
})
168+
}
169+
const m16 = helpers.fromMap16
170+
155171
const codes = nativeEncoder.encode(str)
172+
const mainLength16 = mainLength >> 1
173+
const codes16 = new Uint16Array(codes.buffer, codes.byteOffset, mainLength16)
156174
if (codes.length !== str.length) throw new SyntaxError(E_CHAR) // non-ascii
157-
while (i < mainLength) {
158-
const c0 = codes[i]
159-
const c1 = codes[i + 1]
160-
const c2 = codes[i + 2]
161-
const c3 = codes[i + 3]
175+
176+
// Optional fast loop
177+
for (const mainLength16_2 = mainLength16 - 2; i < mainLength16_2; ) {
178+
const c01 = codes16[i]
179+
const c23 = codes16[i + 1]
180+
const c45 = codes16[i + 2]
181+
const c67 = codes16[i + 3]
182+
const x01 = m16[c01]
183+
const x23 = m16[c23]
184+
const x45 = m16[c45]
185+
const x67 = m16[c67]
186+
if (!x01 && c01 !== _AA || !x23 && c23 !== _AA) throw new SyntaxError(E_CHAR)
187+
if (!x45 && c45 !== _AA || !x67 && c67 !== _AA) throw new SyntaxError(E_CHAR)
188+
arr[at] = x01 >> 4
189+
arr[at + 1] = ((x01 & 0xf) << 4) | (x23 >> 8)
190+
arr[at + 2] = x23 & 0xff
191+
arr[at + 3] = x45 >> 4
192+
arr[at + 4] = ((x45 & 0xf) << 4) | (x67 >> 8)
193+
arr[at + 5] = x67 & 0xff
162194
i += 4
163-
const a = (m[c0] << 18) | (m[c1] << 12) | (m[c2] << 6) | m[c3]
164-
if (a < 0) throw new SyntaxError(E_CHAR)
165-
arr[at] = a >> 16
166-
arr[at + 1] = (a >> 8) & 0xff
167-
arr[at + 2] = a & 0xff
195+
at += 6
196+
}
197+
198+
while (i < mainLength16) {
199+
const c01 = codes16[i]
200+
const c23 = codes16[i + 1]
201+
const x01 = m16[c01]
202+
const x23 = m16[c23]
203+
if (!x01 && c01 !== _AA || !x23 && c23 !== _AA) throw new SyntaxError(E_CHAR)
204+
arr[at] = x01 >> 4
205+
arr[at + 1] = ((x01 & 0xf) << 4) | (x23 >> 8)
206+
arr[at + 2] = x23 & 0xff
207+
i += 2
168208
at += 3
169209
}
210+
i *= 2
170211
} else {
171212
while (i < mainLength) {
172213
const c0 = str.charCodeAt(i)

0 commit comments

Comments
 (0)