mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
perf(bytes): improve performance of equals()
(#4635)
* improve performance of bytes equals * fmt * comment * comment --------- Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
This commit is contained in:
parent
d78321d902
commit
b2115344fd
@ -27,18 +27,38 @@ function equalsNaive(a: Uint8Array, b: Uint8Array): boolean {
|
||||
*/
|
||||
function equals32Bit(a: Uint8Array, b: Uint8Array): boolean {
|
||||
const len = a.length;
|
||||
const compressible = Math.floor(len / 4);
|
||||
const compressedA = new Uint32Array(a, 0, compressible);
|
||||
const compressedB = new Uint32Array(b, 0, compressible);
|
||||
for (let i = compressible * 4; i < len; i++) {
|
||||
const compactOffset = 3 - ((a.byteOffset + 3) % 4);
|
||||
const compactLen = Math.floor((len - compactOffset) / 4);
|
||||
const compactA = new Uint32Array(
|
||||
a.buffer,
|
||||
a.byteOffset + compactOffset,
|
||||
compactLen,
|
||||
);
|
||||
const compactB = new Uint32Array(
|
||||
b.buffer,
|
||||
b.byteOffset + compactOffset,
|
||||
compactLen,
|
||||
);
|
||||
for (let i = 0; i < compactOffset; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
for (let i = 0; i < compressedA.length; i++) {
|
||||
if (compressedA[i] !== compressedB[i]) return false;
|
||||
for (let i = 0; i < compactA.length; i++) {
|
||||
if (compactA[i] !== compactB[i]) return false;
|
||||
}
|
||||
for (let i = compactOffset + compactLen * 4; i < len; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte length threshold for when to use 32-bit comparisons, based on
|
||||
* benchmarks.
|
||||
*
|
||||
* @see {@link https://github.com/denoland/deno_std/pull/4635}
|
||||
*/
|
||||
const THRESHOLD_32_BIT = 160;
|
||||
|
||||
/**
|
||||
* Check whether byte slices are equal to each other.
|
||||
*
|
||||
@ -62,5 +82,8 @@ export function equals(a: Uint8Array, b: Uint8Array): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.length < 1000 ? equalsNaive(a, b) : equals32Bit(a, b);
|
||||
return a.length >= THRESHOLD_32_BIT &&
|
||||
(a.byteOffset % 4) === (b.byteOffset % 4)
|
||||
? equals32Bit(a, b)
|
||||
: equalsNaive(a, b);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ Deno.test("equals()", () => {
|
||||
assert(!v3);
|
||||
});
|
||||
|
||||
const THRESHOLD_32_BIT = 160;
|
||||
|
||||
Deno.test("equals() handles randomized testing", () => {
|
||||
// run tests before and after cutoff
|
||||
for (let len = 995; len <= 1005; len++) {
|
||||
for (let len = THRESHOLD_32_BIT - 10; len <= THRESHOLD_32_BIT + 10; len++) {
|
||||
const arr1 = crypto.getRandomValues(new Uint8Array(len));
|
||||
const arr2 = crypto.getRandomValues(new Uint8Array(len));
|
||||
const arr3 = arr1.slice(0);
|
||||
@ -47,4 +49,20 @@ Deno.test("equals() works with .subarray()", () => {
|
||||
c[999] = 123;
|
||||
assertNotEquals(c, d); // ok
|
||||
assert(!equals(c, d));
|
||||
|
||||
// Test every length/offset combination (modulo 4) to ensure that every byte is checked.
|
||||
for (let offsetA = 0; offsetA < 4; offsetA++) {
|
||||
for (let offsetB = 0; offsetB < 4; offsetB++) {
|
||||
for (let len = THRESHOLD_32_BIT; len < THRESHOLD_32_BIT + 4; len++) {
|
||||
const x = new Uint8Array(new ArrayBuffer(len + offsetA), offsetA);
|
||||
const y = new Uint8Array(new ArrayBuffer(len + offsetB), offsetB);
|
||||
for (let i = 0; i < len; i++) {
|
||||
assert(equals(x, y));
|
||||
x[i] = 1;
|
||||
assert(!equals(x, y));
|
||||
y[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user