-
Notifications
You must be signed in to change notification settings - Fork 632
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(crypto): move FNV hashes from TypeScript to Rust/Wasm, fixing ite…
…rator behaviour and performance
- Loading branch information
1 parent
1341b89
commit e153f43
Showing
8 changed files
with
2,898 additions
and
2,484 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,70 @@ | ||
#!/usr/bin/env -S deno run | ||
#!/usr/bin/env -S deno bench | ||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. | ||
import { assert, assertEquals } from "../../assert/mod.ts"; | ||
import { crypto as stdCrypto, DIGEST_ALGORITHM_NAMES } from "../mod.ts"; | ||
|
||
import { crypto as stdCrypto } from "../mod.ts"; | ||
import { crypto as oldCrypto } from "https://deno.land/std@0.220.1/crypto/mod.ts"; | ||
|
||
const webCrypto = globalThis.crypto; | ||
|
||
// Wasm is limited to 32-bit operations, which SHA-256 is optimized for, while | ||
// SHA-512 is optimized for 64-bit operations and may be slower. | ||
for (const algorithm of ["SHA-256", "SHA-512"] as const) { | ||
for ( | ||
const length of [ | ||
64, | ||
262_144, | ||
4_194_304, | ||
67_108_864, | ||
524_291_328, | ||
] as const | ||
) { | ||
// Create a test input buffer and do some operations to hopefully ensure | ||
// it's fully initialized and not optimized away. | ||
const buffer = new Uint8Array(length); | ||
for (let i = 0; i < length; i++) { | ||
buffer[i] = (i + (i % 13) + (i % 31)) % 255; | ||
} | ||
let sum = 0; | ||
for (const byte of buffer) { | ||
sum += byte; | ||
} | ||
assert(sum > 0); | ||
const BENCHMARKED_DIGEST_ALGORITHM_NAMES = [ | ||
"SHA-256", | ||
"SHA-512", | ||
"BLAKE3", | ||
"FNV32A", | ||
"FNV64A", | ||
] satisfies (typeof DIGEST_ALGORITHM_NAMES[number])[]; | ||
|
||
for ( | ||
const implementation of [ | ||
"runtime WebCrypto (target)", | ||
"std/crypto Wasm (you are here)", | ||
] as const | ||
) { | ||
let lastDigest: ArrayBuffer | undefined; | ||
const WEB_CRYPTO_DIGEST_ALGORITHM_NAMES = [ | ||
"SHA-1", | ||
"SHA-256", | ||
"SHA-384", | ||
"SHA-512", | ||
] satisfies (typeof DIGEST_ALGORITHM_NAMES[number])[]; | ||
|
||
Deno.bench({ | ||
name: `${algorithm.padEnd(12)} ${ | ||
length | ||
.toString() | ||
.padStart(12) | ||
}B ${implementation}`, | ||
async fn() { | ||
let digest; | ||
if (implementation === "std/crypto Wasm (you are here)") { | ||
digest = stdCrypto.subtle.digestSync(algorithm, buffer); | ||
} else if (implementation === "runtime WebCrypto (target)") { | ||
digest = await webCrypto.subtle.digest(algorithm, buffer); | ||
} else { | ||
throw new Error(`Unknown implementation ${implementation}`); | ||
} | ||
for ( | ||
const [length, humanLength] of [ | ||
[64, "64 B"], | ||
[262_144, "256 KiB"], | ||
[4_194_304, "4 MiB"], | ||
[67_108_864, "64 MiB"], | ||
[524_291_328, "512 MiB"], | ||
] as const | ||
) { | ||
const buffer = new Uint8Array(length); | ||
for (let i = 0; i < length; i++) { | ||
buffer[i] = (i + (i % 13) + (i % 31)) % 256; | ||
} | ||
|
||
assert(digest.byteLength > 0); | ||
for (const name of ["FNV32A", "FNV64A"] as const) { | ||
Deno.bench({ | ||
group: `${humanLength} in ${name}`, | ||
name: `TypeScript (from v0.220.1) with ${humanLength} in ${name}`, | ||
async fn() { | ||
await oldCrypto.subtle.digest(name, buffer); | ||
}, | ||
}); | ||
} | ||
|
||
if (lastDigest) { | ||
assertEquals(lastDigest, digest); | ||
} | ||
lastDigest = digest; | ||
for (const name of BENCHMARKED_DIGEST_ALGORITHM_NAMES) { | ||
if ( | ||
(WEB_CRYPTO_DIGEST_ALGORITHM_NAMES as readonly string[]).includes(name) | ||
) { | ||
Deno.bench({ | ||
group: `${humanLength} in ${name}`, | ||
name: `Runtime WebCrypto with ${humanLength} in ${name}`, | ||
async fn() { | ||
await webCrypto.subtle.digest(name, buffer); | ||
}, | ||
}); | ||
} | ||
|
||
Deno.bench({ | ||
group: `${humanLength} in ${name}`, | ||
name: `Rust/Wasm with ${humanLength} in ${name}`, | ||
baseline: true, | ||
async fn() { | ||
await stdCrypto.subtle.digest(name, [buffer]); | ||
}, | ||
}); | ||
} | ||
} |
4,529 changes: 2,279 additions & 2,250 deletions
4,529
crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. | ||
const BASIS_32: u32 = 2166136261; | ||
const PRIME_32: u32 = 16777619; | ||
|
||
const BASIS_64: u64 = 14695981039346656037; | ||
const PRIME_64: u64 = 1099511628211; | ||
|
||
pub struct Fnv32 { | ||
state: u32, | ||
} | ||
|
||
impl Fnv32 { | ||
pub fn new() -> Fnv32 { | ||
Fnv32 { state: BASIS_32 } | ||
} | ||
|
||
pub fn update(&mut self, bytes: impl AsRef<[u8]>) { | ||
for byte in bytes.as_ref() { | ||
self.state = self.state.wrapping_mul(PRIME_32); | ||
self.state ^= u32::from(*byte); | ||
} | ||
} | ||
|
||
pub fn digest(&self) -> Box<[u8]> { | ||
Box::new(self.state.to_be_bytes()) | ||
} | ||
} | ||
|
||
pub struct Fnv32A { | ||
state: u32, | ||
} | ||
|
||
impl Fnv32A { | ||
pub fn new() -> Fnv32A { | ||
Fnv32A { state: BASIS_32 } | ||
} | ||
|
||
pub fn update(&mut self, bytes: impl AsRef<[u8]>) { | ||
for byte in bytes.as_ref() { | ||
self.state ^= u32::from(*byte); | ||
self.state = self.state.wrapping_mul(PRIME_32); | ||
} | ||
} | ||
|
||
pub fn digest(&self) -> Box<[u8]> { | ||
Box::new(self.state.to_be_bytes()) | ||
} | ||
} | ||
|
||
pub struct Fnv64 { | ||
state: u64, | ||
} | ||
|
||
impl Fnv64 { | ||
pub fn new() -> Fnv64 { | ||
Fnv64 { state: BASIS_64 } | ||
} | ||
|
||
pub fn update(&mut self, bytes: impl AsRef<[u8]>) { | ||
for byte in bytes.as_ref() { | ||
self.state = self.state.wrapping_mul(PRIME_64); | ||
self.state ^= u64::from(*byte); | ||
} | ||
} | ||
|
||
pub fn digest(&self) -> Box<[u8]> { | ||
Box::new(self.state.to_be_bytes()) | ||
} | ||
} | ||
|
||
pub struct Fnv64A { | ||
state: u64, | ||
} | ||
|
||
impl Fnv64A { | ||
pub fn new() -> Fnv64A { | ||
Fnv64A { state: BASIS_64 } | ||
} | ||
|
||
pub fn update(&mut self, bytes: impl AsRef<[u8]>) { | ||
for byte in bytes.as_ref() { | ||
self.state ^= u64::from(*byte); | ||
self.state = self.state.wrapping_mul(PRIME_64); | ||
} | ||
} | ||
|
||
pub fn digest(&self) -> Box<[u8]> { | ||
Box::new(self.state.to_be_bytes()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.