From bac8171c40949650d8b01ff6cd2eebf4ec31f1fc Mon Sep 17 00:00:00 2001
From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com>
Date: Thu, 30 Jan 2025 15:38:14 -0800
Subject: [PATCH] perf(crypto): use ring for asm implementations of
sha256/sha512 (#27885)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently we are using the pure rust backend of `sha2`, which has subpar
performance compared to asm implementations. We already depend on
`ring`, so just use that instead of `sha2` for sha256/sha512 digests.
This also speeds up things like S3 uploads, which calculate sha digests
of the uploaded objects. On my local machine, this speeds up uploading a
100MB file (to a localhost s3 provider via`@aws-sdk/client-s3`) by about
2x
Benchmark:
```ts
import { createHmac } from "node:crypto";
for (
const size of [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000]
) {
const input = "a".repeat(size);
Deno.bench({
name: `sha256-${size}`,
fn() {
const _hash = createHmac("sha256", input).update(input).digest();
},
});
Deno.bench({
name: `sha512-${size}`,
fn() {
const _hash = createHmac("sha512", input).update(input).digest();
},
});
}
```
Results (arm64 macOS):
```
--- sha256-1 ---
../../deno/target/release/deno 2.527 µs 1.240 times faster
/Users/nathanwhit/.deno/bin/deno 3.132 µs
--- sha512-1 ---
../../deno/target/release/deno 3.364 µs 1.071 times faster
/Users/nathanwhit/.deno/bin/deno 3.603 µs
--- sha256-10 ---
../../deno/target/release/deno 3.060 µs 1.027 times faster
/Users/nathanwhit/.deno/bin/deno 3.144 µs
--- sha512-10 ---
../../deno/target/release/deno 3.583 µs 1.047 times faster
/Users/nathanwhit/.deno/bin/deno 3.751 µs
--- sha256-100 ---
../../deno/target/release/deno 3.695 µs 1.244 times faster
/Users/nathanwhit/.deno/bin/deno 4.598 µs
--- sha512-100 ---
../../deno/target/release/deno 3.386 µs 1.188 times faster
/Users/nathanwhit/.deno/bin/deno 4.021 µs
--- sha256-1000 ---
../../deno/target/release/deno 4.007 µs 3.230 times faster
/Users/nathanwhit/.deno/bin/deno 12.944 µs
--- sha512-1000 ---
../../deno/target/release/deno 6.463 µs 1.466 times faster
/Users/nathanwhit/.deno/bin/deno 9.477 µs
--- sha256-10000 ---
../../deno/target/release/deno 11.674 µs 6.981 times faster
/Users/nathanwhit/.deno/bin/deno 81.493 µs
--- sha512-10000 ---
../../deno/target/release/deno 31.250 µs 1.740 times faster
/Users/nathanwhit/.deno/bin/deno 54.364 µs
--- sha256-100000 ---
../../deno/target/release/deno 82.800 µs 9.393 times faster
/Users/nathanwhit/.deno/bin/deno 777.719 µs
--- sha512-100000 ---
../../deno/target/release/deno 269.726 µs 1.851 times faster
/Users/nathanwhit/.deno/bin/deno 499.243 µs
--- sha256-1000000 ---
../../deno/target/release/deno 808.662 µs 9.427 times faster
/Users/nathanwhit/.deno/bin/deno 7.623 ms
--- sha512-1000000 ---
../../deno/target/release/deno 2.672 ms 1.795 times faster
/Users/nathanwhit/.deno/bin/deno 4.795 ms
--- sha256-10000000 ---
../../deno/target/release/deno 7.823 ms 9.868 times faster
/Users/nathanwhit/.deno/bin/deno 77.201 ms
--- sha512-10000000 ---
../../deno/target/release/deno 26.197 ms 1.846 times faster
/Users/nathanwhit/.deno/bin/deno 48.356 ms
```
Results (x86_64 linux):
```
--- sha256-1 ---
/home/nathanwhit/.deno/bin/deno 10.726 µs 1.229 times faster
../../../deno/target/release-lite/deno 13.184 µs
--- sha512-1 ---
/home/nathanwhit/.deno/bin/deno 13.177 µs 1.051 times faster
../../../deno/target/release-lite/deno 13.845 µs
--- sha256-10 ---
/home/nathanwhit/.deno/bin/deno 13.156 µs 1.047 times faster
../../../deno/target/release-lite/deno 13.780 µs
--- sha512-10 ---
/home/nathanwhit/.deno/bin/deno 14.386 µs 1.029 times faster
../../../deno/target/release-lite/deno 14.807 µs
--- sha256-100 ---
/home/nathanwhit/.deno/bin/deno 14.580 µs 1.083 times faster
../../../deno/target/release-lite/deno 15.789 µs
--- sha512-100 ---
/home/nathanwhit/.deno/bin/deno 13.477 µs 1.131 times faster
../../../deno/target/release-lite/deno 15.238 µs
--- sha256-1000 ---
../../../deno/target/release-lite/deno 17.208 µs 1.116 times faster
/home/nathanwhit/.deno/bin/deno 19.198 µs
--- sha512-1000 ---
../../../deno/target/release-lite/deno 21.168 µs 1.026 times faster
/home/nathanwhit/.deno/bin/deno 21.717 µs
--- sha256-10000 ---
../../../deno/target/release-lite/deno 33.586 µs 1.990 times faster
/home/nathanwhit/.deno/bin/deno 66.837 µs
--- sha512-10000 ---
../../../deno/target/release-lite/deno 53.338 µs 1.009 times faster
/home/nathanwhit/.deno/bin/deno 53.817 µs
--- sha256-100000 ---
../../../deno/target/release-lite/deno 168.238 µs 3.063 times faster
/home/nathanwhit/.deno/bin/deno 515.354 µs
--- sha512-100000 ---
../../../deno/target/release-lite/deno 383.311 µs 1.036 times faster
/home/nathanwhit/.deno/bin/deno 397.122 µs
--- sha256-1000000 ---
../../../deno/target/release-lite/deno 1.474 ms 3.471 times faster
/home/nathanwhit/.deno/bin/deno 5.115 ms
--- sha512-1000000 ---
../../../deno/target/release-lite/deno 3.658 ms 1.057 times faster
/home/nathanwhit/.deno/bin/deno 3.865 ms
--- sha256-10000000 ---
../../../deno/target/release-lite/deno 16.438 ms 3.136 times faster
/home/nathanwhit/.deno/bin/deno 51.556 ms
--- sha512-10000000 ---
../../../deno/target/release-lite/deno 37.128 ms 1.056 times faster
/home/nathanwhit/.deno/bin/deno 39.220 ms
```
---
ext/node/ops/crypto/digest.rs | 20 ++++++
ext/node/ops/crypto/digest/ring_sha2.rs | 82 +++++++++++++++++++++++++
2 files changed, 102 insertions(+)
create mode 100644 ext/node/ops/crypto/digest/ring_sha2.rs
diff --git a/ext/node/ops/crypto/digest.rs b/ext/node/ops/crypto/digest.rs
index 5f15dace30..9e1fcd21f7 100644
--- a/ext/node/ops/crypto/digest.rs
+++ b/ext/node/ops/crypto/digest.rs
@@ -8,6 +8,8 @@ use digest::DynDigest;
use digest::ExtendableOutput;
use digest::Update;
+mod ring_sha2;
+
pub struct Hasher {
pub hash: Rc>>,
}
@@ -200,6 +202,24 @@ impl Hash {
match algorithm_name {
"shake128" => return Ok(Shake128(Default::default(), output_length)),
"shake256" => return Ok(Shake256(Default::default(), output_length)),
+ "sha256" => {
+ let digest = ring_sha2::RingSha256::new();
+ if let Some(length) = output_length {
+ if length != digest.output_size() {
+ return Err(HashError::OutputLengthMismatch);
+ }
+ }
+ return Ok(Hash::FixedSize(Box::new(digest)));
+ }
+ "sha512" => {
+ let digest = ring_sha2::RingSha512::new();
+ if let Some(length) = output_length {
+ if length != digest.output_size() {
+ return Err(HashError::OutputLengthMismatch);
+ }
+ }
+ return Ok(Hash::FixedSize(Box::new(digest)));
+ }
_ => {}
}
diff --git a/ext/node/ops/crypto/digest/ring_sha2.rs b/ext/node/ops/crypto/digest/ring_sha2.rs
new file mode 100644
index 0000000000..b1d353088b
--- /dev/null
+++ b/ext/node/ops/crypto/digest/ring_sha2.rs
@@ -0,0 +1,82 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+use std::marker::PhantomData;
+
+use digest::generic_array::ArrayLength;
+
+pub trait RingDigestAlgo {
+ fn algorithm() -> &'static ring::digest::Algorithm;
+ type OutputSize: ArrayLength + 'static;
+}
+
+pub struct RingDigest {
+ context: ring::digest::Context,
+ _phantom: PhantomData,
+}
+
+impl Clone for RingDigest {
+ fn clone(&self) -> Self {
+ Self {
+ context: self.context.clone(),
+ _phantom: self._phantom,
+ }
+ }
+}
+
+impl digest::HashMarker for RingDigest {}
+impl Default for RingDigest {
+ fn default() -> Self {
+ Self {
+ context: ring::digest::Context::new(Algo::algorithm()),
+ _phantom: PhantomData,
+ }
+ }
+}
+impl digest::Reset for RingDigest {
+ fn reset(&mut self) {
+ self.context = ring::digest::Context::new(Algo::algorithm())
+ }
+}
+impl digest::Update for RingDigest {
+ fn update(&mut self, data: &[u8]) {
+ self.context.update(data);
+ }
+}
+impl digest::OutputSizeUser for RingDigest {
+ type OutputSize = Algo::OutputSize;
+}
+impl digest::FixedOutput for RingDigest {
+ fn finalize_into(self, out: &mut digest::Output) {
+ let result = self.context.finish();
+ out.copy_from_slice(result.as_ref());
+ }
+}
+impl digest::FixedOutputReset for RingDigest {
+ fn finalize_into_reset(&mut self, out: &mut digest::Output) {
+ let context = std::mem::replace(
+ &mut self.context,
+ ring::digest::Context::new(Algo::algorithm()),
+ );
+ out.copy_from_slice(context.finish().as_ref());
+ }
+}
+
+pub struct RingSha256Algo;
+impl RingDigestAlgo for RingSha256Algo {
+ fn algorithm() -> &'static ring::digest::Algorithm {
+ &ring::digest::SHA256
+ }
+
+ type OutputSize = digest::typenum::U32;
+}
+pub struct RingSha512Algo;
+impl RingDigestAlgo for RingSha512Algo {
+ fn algorithm() -> &'static ring::digest::Algorithm {
+ &ring::digest::SHA512
+ }
+
+ type OutputSize = digest::typenum::U64;
+}
+
+pub type RingSha256 = RingDigest;
+pub type RingSha512 = RingDigest;