summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrandomicon00 <20146907+randomicon00@users.noreply.github.com>2022-05-17 14:52:48 +0100
committerGitHub <noreply@github.com>2022-05-17 19:22:48 +0530
commite58f77e431c7f9404bd76f6b7cf5b6d7841c0b0f (patch)
treeba49dbb466b24c7816ba1424ad11d7292d9340ef
parent8879244f72aebd2fd4d3ae34a064ff04069a6b58 (diff)
perf(ext/web): Add fast path for non-streaming TextDecoder (#14217)
Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
-rw-r--r--ext/web/08_text_encoding.js28
-rw-r--r--ext/web/Cargo.toml4
-rw-r--r--ext/web/benches/encoding.rs54
-rw-r--r--ext/web/lib.rs59
4 files changed, 134 insertions, 11 deletions
diff --git a/ext/web/08_text_encoding.js b/ext/web/08_text_encoding.js
index 4f26089b1..86ac5ab3c 100644
--- a/ext/web/08_text_encoding.js
+++ b/ext/web/08_text_encoding.js
@@ -95,16 +95,6 @@
context: "Argument 2",
});
- // TODO(lucacasonato): add fast path for non-streaming decoder & decode
-
- if (this.#rid === null) {
- this.#rid = core.opSync("op_encoding_new_decoder", {
- label: this.#encoding,
- fatal: this.#fatal,
- ignoreBom: this.#ignoreBOM,
- });
- }
-
try {
try {
if (ArrayBufferIsView(input)) {
@@ -132,12 +122,28 @@
// with a TypedArray argument copies the data.
input = new Uint8Array(input);
}
+
+ if (!options.stream && this.#rid === null) {
+ return core.opSync("op_encoding_decode_single", input, {
+ label: this.#encoding,
+ fatal: this.#fatal,
+ ignoreBom: this.#ignoreBOM,
+ });
+ }
+
+ if (this.#rid === null) {
+ this.#rid = core.opSync("op_encoding_new_decoder", {
+ label: this.#encoding,
+ fatal: this.#fatal,
+ ignoreBom: this.#ignoreBOM,
+ });
+ }
return core.opSync("op_encoding_decode", input, {
rid: this.#rid,
stream: options.stream,
});
} finally {
- if (!options.stream) {
+ if (!options.stream && this.#rid) {
core.close(this.#rid);
this.#rid = null;
}
diff --git a/ext/web/Cargo.toml b/ext/web/Cargo.toml
index 27d4d8147..08cd5d7b0 100644
--- a/ext/web/Cargo.toml
+++ b/ext/web/Cargo.toml
@@ -29,5 +29,9 @@ deno_url = { version = "0.52.0", path = "../url" }
deno_webidl = { version = "0.52.0", path = "../webidl" }
[[bench]]
+name = "encoding"
+harness = false
+
+[[bench]]
name = "timers_ops"
harness = false
diff --git a/ext/web/benches/encoding.rs b/ext/web/benches/encoding.rs
new file mode 100644
index 000000000..5b8b2a988
--- /dev/null
+++ b/ext/web/benches/encoding.rs
@@ -0,0 +1,54 @@
+use deno_core::Extension;
+
+use deno_bench_util::bench_js_sync;
+use deno_bench_util::bench_or_profile;
+use deno_bench_util::bencher::{benchmark_group, Bencher};
+use deno_web::BlobStore;
+
+struct Permissions;
+
+impl deno_web::TimersPermission for Permissions {
+ fn allow_hrtime(&mut self) -> bool {
+ false
+ }
+ fn check_unstable(
+ &self,
+ _state: &deno_core::OpState,
+ _api_name: &'static str,
+ ) {
+ unreachable!()
+ }
+}
+
+fn setup() -> Vec<Extension> {
+ vec![
+ deno_webidl::init(),
+ deno_url::init(),
+ deno_web::init::<Permissions>(BlobStore::default(), None),
+ Extension::builder()
+ .js(vec![(
+ "setup",
+ Box::new(|| {
+ Ok(
+ r#"
+ const { TextDecoder } = globalThis.__bootstrap.encoding;
+ const hello12k = Deno.core.encode("hello world\n".repeat(1e3));
+ "#
+ .to_owned(),
+ )
+ }),
+ )])
+ .state(|state| {
+ state.put(Permissions {});
+ Ok(())
+ })
+ .build(),
+ ]
+}
+
+fn bench_encode_12kb(b: &mut Bencher) {
+ bench_js_sync(b, r#"new TextDecoder().decode(hello12k);"#, setup);
+}
+
+benchmark_group!(benches, bench_encode_12kb);
+bench_or_profile!(benches);
diff --git a/ext/web/lib.rs b/ext/web/lib.rs
index 0d57bf10e..ae5cf2d80 100644
--- a/ext/web/lib.rs
+++ b/ext/web/lib.rs
@@ -90,6 +90,7 @@ pub fn init<P: TimersPermission + 'static>(
op_base64_atob::decl(),
op_base64_btoa::decl(),
op_encoding_normalize_label::decl(),
+ op_encoding_decode_single::decl(),
op_encoding_new_decoder::decl(),
op_encoding_decode::decl(),
op_encoding_encode_into::decl(),
@@ -216,6 +217,64 @@ fn op_encoding_normalize_label(label: String) -> Result<String, AnyError> {
}
#[op]
+fn op_encoding_decode_single(
+ data: ZeroCopyBuf,
+ options: DecoderOptions,
+) -> Result<U16String, AnyError> {
+ let DecoderOptions {
+ label,
+ ignore_bom,
+ fatal,
+ } = options;
+
+ let encoding = Encoding::for_label(label.as_bytes()).ok_or_else(|| {
+ range_error(format!(
+ "The encoding label provided ('{}') is invalid.",
+ label
+ ))
+ })?;
+
+ let mut decoder = if ignore_bom {
+ encoding.new_decoder_without_bom_handling()
+ } else {
+ encoding.new_decoder_with_bom_removal()
+ };
+
+ let max_buffer_length = decoder
+ .max_utf16_buffer_length(data.len())
+ .ok_or_else(|| range_error("Value too large to decode."))?;
+
+ let mut output = vec![0; max_buffer_length];
+
+ if fatal {
+ let (result, _, written) =
+ decoder.decode_to_utf16_without_replacement(&data, &mut output, true);
+ match result {
+ DecoderResult::InputEmpty => {
+ output.truncate(written);
+ Ok(output.into())
+ }
+ DecoderResult::OutputFull => {
+ Err(range_error("Provided buffer too small."))
+ }
+ DecoderResult::Malformed(_, _) => {
+ Err(type_error("The encoded data is not valid."))
+ }
+ }
+ } else {
+ let (result, _, written, _) =
+ decoder.decode_to_utf16(&data, &mut output, true);
+ match result {
+ CoderResult::InputEmpty => {
+ output.truncate(written);
+ Ok(output.into())
+ }
+ CoderResult::OutputFull => Err(range_error("Provided buffer too small.")),
+ }
+ }
+}
+
+#[op]
fn op_encoding_new_decoder(
state: &mut OpState,
options: DecoderOptions,