From e0a79c055086c01a4c8f295916e34d53013346eb Mon Sep 17 00:00:00 2001 From: Nugine Date: Wed, 29 Jun 2022 23:42:39 +0800 Subject: [PATCH] perf(ext/web): use base64-simd for atob/btoa (#14992) --- Cargo.lock | 17 +++++++++++- Cargo.toml | 4 +++ ext/web/Cargo.toml | 2 +- ext/web/lib.rs | 69 ++++++++++++---------------------------------- 4 files changed, 39 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0910acd14b..07449b7a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64-simd" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278c7ba87265587c4823cf1b2fdf57834151540b2e509574adb03627f8c7f22d" +dependencies = [ + "simd-abstraction", +] + [[package]] name = "base64ct" version = "1.5.0" @@ -1202,7 +1211,7 @@ name = "deno_web" version = "0.89.0" dependencies = [ "async-trait", - "base64 0.13.0", + "base64-simd", "deno_bench_util", "deno_core", "deno_url", @@ -3893,6 +3902,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-abstraction" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2880f3f7b392823ee65bbcc681961cd8e698c6a30e91ab9b4eef1f9c6c226d8" + [[package]] name = "siphasher" version = "0.3.10" diff --git a/Cargo.toml b/Cargo.toml index 12a8b2cce7..071947c01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,8 @@ opt-level = 3 opt-level = 3 [profile.bench.package.zstd-sys] opt-level = 3 +[profile.bench.package.base64-simd] +opt-level = 3 # NB: the `bench` and `release` profiles must remain EXACTLY the same. [profile.release.package.rand] @@ -174,3 +176,5 @@ opt-level = 3 opt-level = 3 [profile.release.package.zstd-sys] opt-level = 3 +[profile.release.package.base64-simd] +opt-level = 3 diff --git a/ext/web/Cargo.toml b/ext/web/Cargo.toml index ada3faab68..e321f4fb69 100644 --- a/ext/web/Cargo.toml +++ b/ext/web/Cargo.toml @@ -15,7 +15,7 @@ path = "lib.rs" [dependencies] async-trait = "0.1.51" -base64 = "0.13.0" +base64-simd = "0.6.2" deno_core = { version = "0.140.0", path = "../../core" } encoding_rs = "0.8.31" flate2 = "1" diff --git a/ext/web/lib.rs b/ext/web/lib.rs index b21ea38126..9bc7c1ccec 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -124,74 +124,41 @@ pub fn init( #[op] fn op_base64_decode(input: String) -> Result { - let mut input = input.into_bytes(); - input.retain(|c| !c.is_ascii_whitespace()); - Ok(b64_decode(&input)?.into()) + Ok(forgiving_base64_decode(input.into_bytes())?.into()) } #[op] -fn op_base64_atob(mut s: ByteString) -> Result { - 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(b64_decode(&s)?.into()) +fn op_base64_atob(s: ByteString) -> Result { + Ok(forgiving_base64_decode(s.into())?.into()) } -fn b64_decode(input: &[u8]) -> Result, AnyError> { - // "If the length of input divides by 4 leaving no remainder, then: - // if input ends with one or two U+003D EQUALS SIGN (=) characters, - // remove them from input." - let input = match input.len() % 4 == 0 { - true if input.ends_with(b"==") => &input[..input.len() - 2], - true if input.ends_with(b"=") => &input[..input.len() - 1], - _ => input, - }; +/// See +fn forgiving_base64_decode(mut input: Vec) -> Result, AnyError> { + let error: _ = + || DomExceptionInvalidCharacterError::new("Failed to decode base64"); - // "If the length of input divides by 4 leaving a remainder of 1, - // throw an InvalidCharacterError exception and abort these steps." - if input.len() % 4 == 1 { - return Err( - DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(), - ); - } + let decoded = base64_simd::Base64::forgiving_decode_inplace(&mut input) + .map_err(|_| error())?; - let cfg = base64::Config::new(base64::CharacterSet::Standard, true) - .decode_allow_trailing_bits(true); - let out = base64::decode_config(input, cfg).map_err(|err| match err { - base64::DecodeError::InvalidByte(_, _) => { - DomExceptionInvalidCharacterError::new( - "Failed to decode base64: invalid character", - ) - } - _ => DomExceptionInvalidCharacterError::new(&format!( - "Failed to decode base64: {:?}", - err - )), - })?; - - Ok(out) + let decoded_len = decoded.len(); + input.truncate(decoded_len); + Ok(input) } #[op] fn op_base64_encode(s: ZeroCopyBuf) -> String { - b64_encode(&s) + forgiving_base64_encode(s.as_ref()) } #[op] fn op_base64_btoa(s: ByteString) -> String { - b64_encode(s) + forgiving_base64_encode(s.as_ref()) } -fn b64_encode(s: impl AsRef<[u8]>) -> String { - let cfg = base64::Config::new(base64::CharacterSet::Standard, true) - .decode_allow_trailing_bits(true); - base64::encode_config(s.as_ref(), cfg) +/// See +fn forgiving_base64_encode(s: &[u8]) -> String { + let base64 = base64_simd::Base64::STANDARD; + base64.encode_to_boxed_str(s).into_string() } #[derive(Deserialize)]