From 4451fa857bf4e9f021c8c63d3944774e8c9b337f Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 6 Mar 2023 08:58:04 +0530 Subject: [PATCH] perf(ext/node): improve createHash performance (#18033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` > deno run -A ../test.mjs cpu: unknown runtime: deno 1.31.1 (aarch64-apple-darwin) benchmark time (avg) (min … max) p75 p99 p995 ------------------------------------------------- ----------------------------- 2.22 µs/iter (2.2 µs … 2.28 µs) 2.22 µs 2.28 µs 2.28 µs > target/release/deno run -A test.mjs cpu: unknown runtime: deno 1.31.1 (aarch64-apple-darwin) benchmark time (avg) (min … max) p75 p99 p995 ------------------------------------------------- ----------------------------- 864.9 ns/iter (825.05 ns … 1.22 µs) 864.93 ns 1.22 µs 1.22 µs ``` --- Cargo.lock | 7 +++ ext/node/Cargo.toml | 1 + ext/node/crypto/mod.rs | 55 ++++++++++++++++------ ext/node/lib.rs | 2 + ext/node/polyfills/internal/crypto/hash.ts | 32 ++++++++----- 5 files changed, 70 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c6c30f3de..8b9dd991fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1228,6 +1228,7 @@ version = "0.28.0" dependencies = [ "deno_core", "digest 0.10.6", + "hex", "idna 0.3.0", "indexmap", "md-5", @@ -2241,6 +2242,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexf-parse" version = "0.2.1" diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index e21c8c3040..1ce6dd443b 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -16,6 +16,7 @@ path = "lib.rs" [dependencies] deno_core.workspace = true digest = { version = "0.10.5", features = ["core-api", "std"] } +hex = "0.4.3" idna = "0.3.0" indexmap.workspace = true md-5 = "0.10.5" diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs index 597779328f..f9a61b4561 100644 --- a/ext/node/crypto/mod.rs +++ b/ext/node/crypto/mod.rs @@ -17,23 +17,38 @@ use rsa::RsaPublicKey; mod digest; -#[op] -pub fn op_node_create_hash( - state: &mut OpState, - algorithm: String, -) -> Result { - Ok(state.resource_table.add(digest::Context::new(&algorithm)?)) +#[op(fast)] +pub fn op_node_create_hash(state: &mut OpState, algorithm: &str) -> u32 { + state + .resource_table + .add(match digest::Context::new(algorithm) { + Ok(context) => context, + Err(_) => return 0, + }) } -#[op] -pub fn op_node_hash_update( - state: &mut OpState, - rid: ResourceId, - data: &[u8], -) -> Result<(), AnyError> { - let context = state.resource_table.get::(rid)?; +#[op(fast)] +pub fn op_node_hash_update(state: &mut OpState, rid: u32, data: &[u8]) -> bool { + let context = match state.resource_table.get::(rid) { + Ok(context) => context, + _ => return false, + }; context.update(data); - Ok(()) + true +} + +#[op(fast)] +pub fn op_node_hash_update_str( + state: &mut OpState, + rid: u32, + data: &str, +) -> bool { + let context = match state.resource_table.get::(rid) { + Ok(context) => context, + _ => return false, + }; + context.update(data.as_bytes()); + true } #[op] @@ -47,6 +62,18 @@ pub fn op_node_hash_digest( Ok(context.digest()?.into()) } +#[op] +pub fn op_node_hash_digest_hex( + state: &mut OpState, + rid: ResourceId, +) -> Result { + let context = state.resource_table.take::(rid)?; + let context = Rc::try_unwrap(context) + .map_err(|_| type_error("Hash context is already in use"))?; + let digest = context.digest()?; + Ok(hex::encode(digest)) +} + #[op] pub fn op_node_hash_clone( state: &mut OpState, diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 6df408ffd0..725567cc38 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -328,7 +328,9 @@ pub fn init_polyfill() -> Extension { .ops(vec![ crypto::op_node_create_hash::decl(), crypto::op_node_hash_update::decl(), + crypto::op_node_hash_update_str::decl(), crypto::op_node_hash_digest::decl(), + crypto::op_node_hash_digest_hex::decl(), crypto::op_node_hash_clone::decl(), crypto::op_node_private_encrypt::decl(), crypto::op_node_private_decrypt::decl(), diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts index 7a7c0be8ea..e2907632f6 100644 --- a/ext/node/polyfills/internal/crypto/hash.ts +++ b/ext/node/polyfills/internal/crypto/hash.ts @@ -1,13 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. -import { - TextDecoder, - TextEncoder, -} from "internal:deno_web/08_text_encoding.js"; +import { TextEncoder } from "internal:deno_web/08_text_encoding.js"; import { Buffer } from "internal:deno_node/buffer.ts"; import { Transform } from "internal:deno_node/stream.ts"; -import { encode as encodeToHex } from "internal:deno_node/internal/crypto/_hex.ts"; import { forgivingBase64Encode as encodeToBase64, forgivingBase64UrlEncode as encodeToBase64Url, @@ -26,6 +22,14 @@ import { notImplemented } from "internal:deno_node/_utils.ts"; const { ops } = globalThis.__bootstrap.core; +// TODO(@littledivy): Use Result instead of boolean when +// https://bugs.chromium.org/p/v8/issues/detail?id=13600 is fixed. +function unwrapErr(ok: boolean) { + if (!ok) { + throw new Error("Context is not initialized"); + } +} + const coerceToBytes = (data: string | BufferSource): Uint8Array => { if (data instanceof Uint8Array) { return data; @@ -71,6 +75,9 @@ export class Hash extends Transform { this.#context = ops.op_node_create_hash( algorithm, ); + if (this.#context === 0) { + throw new TypeError(`Unknown hash algorithm: ${algorithm}`); + } } else { this.#context = algorithm; } @@ -86,16 +93,12 @@ export class Hash extends Transform { * Updates the hash content with the given data. */ update(data: string | ArrayBuffer, _encoding?: string): this { - let bytes; if (typeof data === "string") { - data = new TextEncoder().encode(data); - bytes = coerceToBytes(data); + unwrapErr(ops.op_node_hash_update_str(this.#context, data)); } else { - bytes = coerceToBytes(data); + unwrapErr(ops.op_node_hash_update(this.#context, coerceToBytes(data))); } - ops.op_node_hash_update(this.#context, bytes); - return this; } @@ -107,14 +110,17 @@ export class Hash extends Transform { * Supported encodings are currently 'hex', 'binary', 'base64', 'base64url'. */ digest(encoding?: string): Buffer | string { + if (encoding === "hex") { + return ops.op_node_hash_digest_hex(this.#context); + } + const digest = ops.op_node_hash_digest(this.#context); if (encoding === undefined) { return Buffer.from(digest); } + // TODO(@littedivy): Fast paths for below encodings. switch (encoding) { - case "hex": - return new TextDecoder().decode(encodeToHex(new Uint8Array(digest))); case "binary": return String.fromCharCode(...digest); case "base64":