mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
perf(ext/web): optimize atob/btoa (#13841)
Follow up to #13839, optimizing `base64_roundtrip` ~20x (~125ms => ~6.5ms)
This commit is contained in:
parent
96dc7421ae
commit
72d593fc5c
3 changed files with 83 additions and 64 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -263,7 +263,7 @@ jobs:
|
||||||
~/.cargo/registry/index
|
~/.cargo/registry/index
|
||||||
~/.cargo/registry/cache
|
~/.cargo/registry/cache
|
||||||
~/.cargo/git/db
|
~/.cargo/git/db
|
||||||
key: 4-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
|
key: 5-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
# In main branch, always creates fresh cache
|
# In main branch, always creates fresh cache
|
||||||
- name: Cache build output (main)
|
- name: Cache build output (main)
|
||||||
|
@ -279,7 +279,7 @@ jobs:
|
||||||
!./target/*/*.zip
|
!./target/*/*.zip
|
||||||
!./target/*/*.tar.gz
|
!./target/*/*.tar.gz
|
||||||
key: |
|
key: |
|
||||||
4-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
|
5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
|
||||||
|
|
||||||
# Restore cache from the latest 'main' branch build.
|
# Restore cache from the latest 'main' branch build.
|
||||||
- name: Cache build output (PR)
|
- name: Cache build output (PR)
|
||||||
|
@ -295,7 +295,7 @@ jobs:
|
||||||
!./target/*/*.tar.gz
|
!./target/*/*.tar.gz
|
||||||
key: never_saved
|
key: never_saved
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
4-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
|
5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
|
||||||
|
|
||||||
# Don't save cache after building PRs or branches other than 'main'.
|
# Don't save cache after building PRs or branches other than 'main'.
|
||||||
- name: Skip save cache (PR)
|
- name: Skip save cache (PR)
|
||||||
|
|
|
@ -9,21 +9,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
|
const core = Deno.core;
|
||||||
const webidl = window.__bootstrap.webidl;
|
const webidl = window.__bootstrap.webidl;
|
||||||
const {
|
|
||||||
forgivingBase64Encode,
|
|
||||||
forgivingBase64Decode,
|
|
||||||
} = window.__bootstrap.infra;
|
|
||||||
const { DOMException } = window.__bootstrap.domException;
|
const { DOMException } = window.__bootstrap.domException;
|
||||||
const {
|
const { TypeError } = window.__bootstrap.primordials;
|
||||||
ArrayPrototypeMap,
|
|
||||||
StringPrototypeCharCodeAt,
|
|
||||||
ArrayPrototypeJoin,
|
|
||||||
SafeArrayIterator,
|
|
||||||
StringFromCharCode,
|
|
||||||
TypedArrayFrom,
|
|
||||||
Uint8Array,
|
|
||||||
} = window.__bootstrap.primordials;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} data
|
* @param {string} data
|
||||||
|
@ -36,13 +25,17 @@
|
||||||
prefix,
|
prefix,
|
||||||
context: "Argument 1",
|
context: "Argument 1",
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
const uint8Array = forgivingBase64Decode(data);
|
return core.opSync("op_base64_atob", data);
|
||||||
const result = ArrayPrototypeMap(
|
} catch (e) {
|
||||||
[...new SafeArrayIterator(uint8Array)],
|
if (e instanceof TypeError) {
|
||||||
(byte) => StringFromCharCode(byte),
|
throw new DOMException(
|
||||||
);
|
"Failed to decode base64: invalid character",
|
||||||
return ArrayPrototypeJoin(result, "");
|
"InvalidCharacterError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,20 +49,17 @@
|
||||||
prefix,
|
prefix,
|
||||||
context: "Argument 1",
|
context: "Argument 1",
|
||||||
});
|
});
|
||||||
const byteArray = ArrayPrototypeMap(
|
try {
|
||||||
[...new SafeArrayIterator(data)],
|
return core.opSync("op_base64_btoa", data);
|
||||||
(char) => {
|
} catch (e) {
|
||||||
const charCode = StringPrototypeCharCodeAt(char, 0);
|
if (e instanceof TypeError) {
|
||||||
if (charCode > 0xff) {
|
throw new DOMException(
|
||||||
throw new DOMException(
|
"The string to be encoded contains characters outside of the Latin1 range.",
|
||||||
"The string to be encoded contains characters outside of the Latin1 range.",
|
"InvalidCharacterError",
|
||||||
"InvalidCharacterError",
|
);
|
||||||
);
|
}
|
||||||
}
|
throw e;
|
||||||
return charCode;
|
}
|
||||||
},
|
|
||||||
);
|
|
||||||
return forgivingBase64Encode(TypedArrayFrom(Uint8Array, byteArray));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__bootstrap.base64 = {
|
window.__bootstrap.base64 = {
|
||||||
|
|
|
@ -12,6 +12,7 @@ use deno_core::include_js_files;
|
||||||
use deno_core::op_async;
|
use deno_core::op_async;
|
||||||
use deno_core::op_sync;
|
use deno_core::op_sync;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
|
use deno_core::ByteString;
|
||||||
use deno_core::Extension;
|
use deno_core::Extension;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
|
@ -85,6 +86,8 @@ pub fn init<P: TimersPermission + 'static>(
|
||||||
.ops(vec![
|
.ops(vec![
|
||||||
("op_base64_decode", op_sync(op_base64_decode)),
|
("op_base64_decode", op_sync(op_base64_decode)),
|
||||||
("op_base64_encode", op_sync(op_base64_encode)),
|
("op_base64_encode", op_sync(op_base64_encode)),
|
||||||
|
("op_base64_atob", op_sync(op_base64_atob)),
|
||||||
|
("op_base64_btoa", op_sync(op_base64_btoa)),
|
||||||
(
|
(
|
||||||
"op_encoding_normalize_label",
|
"op_encoding_normalize_label",
|
||||||
op_sync(op_encoding_normalize_label),
|
op_sync(op_encoding_normalize_label),
|
||||||
|
@ -146,21 +149,42 @@ pub fn init<P: TimersPermission + 'static>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn op_base64_decode(
|
fn op_base64_decode(
|
||||||
_state: &mut OpState,
|
_: &mut OpState,
|
||||||
input: String,
|
input: String,
|
||||||
_: (),
|
_: (),
|
||||||
) -> Result<ZeroCopyBuf, AnyError> {
|
) -> Result<ZeroCopyBuf, AnyError> {
|
||||||
let mut input: &str = &input.replace(|c| char::is_ascii_whitespace(&c), "");
|
let mut input = input.into_bytes();
|
||||||
|
input.retain(|c| !c.is_ascii_whitespace());
|
||||||
|
Ok(b64_decode(&input)?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_base64_atob(
|
||||||
|
_: &mut OpState,
|
||||||
|
s: ByteString,
|
||||||
|
_: (),
|
||||||
|
) -> Result<ByteString, AnyError> {
|
||||||
|
let mut s = s.0;
|
||||||
|
s.retain(|c| !c.is_ascii_whitespace());
|
||||||
|
|
||||||
|
// If padding is expected, fail if not 4-byte aligned
|
||||||
|
if s.len() % 4 != 0 && (s.ends_with(b"==") || s.ends_with(b"=")) {
|
||||||
|
return Err(
|
||||||
|
DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ByteString(b64_decode(&s)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b64_decode(input: &[u8]) -> Result<Vec<u8>, AnyError> {
|
||||||
// "If the length of input divides by 4 leaving no remainder, then:
|
// "If the length of input divides by 4 leaving no remainder, then:
|
||||||
// if input ends with one or two U+003D EQUALS SIGN (=) characters,
|
// if input ends with one or two U+003D EQUALS SIGN (=) characters,
|
||||||
// remove them from input."
|
// remove them from input."
|
||||||
if input.len() % 4 == 0 {
|
let input = match input.len() % 4 == 0 {
|
||||||
if input.ends_with("==") {
|
true if input.ends_with(b"==") => &input[..input.len() - 2],
|
||||||
input = &input[..input.len() - 2]
|
true if input.ends_with(b"=") => &input[..input.len() - 1],
|
||||||
} else if input.ends_with('=') {
|
_ => input,
|
||||||
input = &input[..input.len() - 1]
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "If the length of input divides by 4 leaving a remainder of 1,
|
// "If the length of input divides by 4 leaving a remainder of 1,
|
||||||
// throw an InvalidCharacterError exception and abort these steps."
|
// throw an InvalidCharacterError exception and abort these steps."
|
||||||
|
@ -170,38 +194,43 @@ fn op_base64_decode(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if input
|
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
||||||
.chars()
|
.decode_allow_trailing_bits(true);
|
||||||
.any(|c| c != '+' && c != '/' && !c.is_alphanumeric())
|
let out = base64::decode_config(input, cfg).map_err(|err| match err {
|
||||||
{
|
base64::DecodeError::InvalidByte(_, _) => {
|
||||||
return Err(
|
|
||||||
DomExceptionInvalidCharacterError::new(
|
DomExceptionInvalidCharacterError::new(
|
||||||
"Failed to decode base64: invalid character",
|
"Failed to decode base64: invalid character",
|
||||||
)
|
)
|
||||||
.into(),
|
}
|
||||||
);
|
_ => DomExceptionInvalidCharacterError::new(&format!(
|
||||||
}
|
|
||||||
|
|
||||||
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
|
||||||
.decode_allow_trailing_bits(true);
|
|
||||||
let out = base64::decode_config(&input, cfg).map_err(|err| {
|
|
||||||
DomExceptionInvalidCharacterError::new(&format!(
|
|
||||||
"Failed to decode base64: {:?}",
|
"Failed to decode base64: {:?}",
|
||||||
err
|
err
|
||||||
))
|
)),
|
||||||
})?;
|
})?;
|
||||||
Ok(ZeroCopyBuf::from(out))
|
|
||||||
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn op_base64_encode(
|
fn op_base64_encode(
|
||||||
_state: &mut OpState,
|
_: &mut OpState,
|
||||||
s: ZeroCopyBuf,
|
s: ZeroCopyBuf,
|
||||||
_: (),
|
_: (),
|
||||||
) -> Result<String, AnyError> {
|
) -> Result<String, AnyError> {
|
||||||
|
Ok(b64_encode(&s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_base64_btoa(
|
||||||
|
_: &mut OpState,
|
||||||
|
s: ByteString,
|
||||||
|
_: (),
|
||||||
|
) -> Result<String, AnyError> {
|
||||||
|
Ok(b64_encode(&s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b64_encode(s: impl AsRef<[u8]>) -> String {
|
||||||
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
let cfg = base64::Config::new(base64::CharacterSet::Standard, true)
|
||||||
.decode_allow_trailing_bits(true);
|
.decode_allow_trailing_bits(true);
|
||||||
let out = base64::encode_config(&s, cfg);
|
base64::encode_config(s.as_ref(), cfg)
|
||||||
Ok(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
Loading…
Reference in a new issue