Use DataView as applicable (#1186)

* Use DataView as applicable

* .skip

* Fix hexToU8a length calc

* hello world
This commit is contained in:
Jaco
2021-11-15 13:01:47 +02:00
committed by GitHub
parent 5972551313
commit de7817e6b5
7 changed files with 81 additions and 71 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ Changes:
- Add strongly typed `object{Entries, Keys, Values}` utilities
- Expand tests for uncovered functions
- Sync with upstream Substrate ss58 registry
- Optimizations for hex <-> u8a functions
- Allow `u8aEq` to use `Uint{8, 16, 32}Array` with correct alignment
- Optimize for `hexToU8a` and `u8aToHex` functions
- Optimize `u8aEq` to use `DataView` for compares
## 7.8.2 Nov 7, 2021
+1 -1
View File
@@ -17,7 +17,7 @@ for (let n = 0; n < 256; n++) {
for (let i = 0; i < 256; i++) {
for (let j = 0; j < 256; j++) {
const hex = U8_TO_HEX[i] + U8_TO_HEX[j];
const n = (j * 256) + i;
const n = (i * 256) + j;
U16_TO_HEX[n] = hex;
HEX_TO_U16[hex] = n;
+6
View File
@@ -35,6 +35,12 @@ describe('hexToU8a', (): void => {
);
});
it('converts known bytes to their correct values', (): void => {
expect(
hexToU8a('0x68656c6c6f20776f726c64') // hello world (11 bytes, non-aligned)
).toEqual(new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64]));
});
it('fails when non-hex value provided', (): void => {
expect(
() => hexToU8a('notahex')
+12 -24
View File
@@ -28,37 +28,25 @@ export function hexToU8a (_value?: HexString | string | null, bitLength = -1): U
const value = hexStripPrefix(_value).toLowerCase();
const valLength = value.length / 2;
const resultLength = Math.ceil(
const endLength = Math.ceil(
bitLength === -1
? valLength
: bitLength / 8
);
const result = new Uint8Array(resultLength);
const offset = resultLength > valLength
? resultLength - valLength
const result = new Uint8Array(endLength);
const offset = endLength > valLength
? endLength - valLength
: 0;
const dv = new DataView(result.buffer, offset);
const mod = (endLength - offset) % 2;
const length = endLength - offset - mod;
// With offsets, it becomes slightly trickier since we need to ensure the U16
// is at the correct alignment as well. Due to this, we just focus on the simple
// case where we do no bitlength conversions (which is the most-taken path)
if (offset) {
for (let i = 0; i < resultLength; i++) {
result[i + offset] = HEX_TO_U8[value.substr(i * 2, 2)];
}
} else {
// NOTE: It is only _slightly_ more optimal to use HEX_TO_U16 here as
// well. The overhead does not seem to be in the lookup map iteration,
// even with 640k inputs
const mod = resultLength % 2;
const u16 = new Uint16Array(result.buffer, 0, (resultLength - mod) / 2);
for (let i = 0; i < length; i += 2) {
dv.setUint16(i, HEX_TO_U16[value.substr(i * 2, 4)]);
}
for (let i = 0; i < u16.length; i++) {
u16[i] = HEX_TO_U16[value.substr(i * 4, 4)];
}
if (mod) {
result[resultLength - 1] = HEX_TO_U8[value.substr(value.length - 2, 2)];
}
if (mod) {
dv.setUint8(length, HEX_TO_U8[value.substr(value.length - 2, 2)]);
}
return result;
+36
View File
@@ -31,6 +31,42 @@ describe('u8aEq', (): void => {
).toEqual(true);
});
it('returns false when the contents do not match (with u32 start)', (): void => {
expect(
u8aEq(
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3]),
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 4])
)
).toEqual(false);
});
it('returns true when the contents do match (with u32 start)', (): void => {
expect(
u8aEq(
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3]),
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3])
)
).toEqual(true);
});
it('returns false when the contents do not match (u32 only)', (): void => {
expect(
u8aEq(
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 9])
)
).toEqual(false);
});
it('returns true when the contents do match (u32 only)', (): void => {
expect(
u8aEq(
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
)
).toEqual(true);
});
it('returns true with equalvalent hex & Uint8array', (): void => {
expect(
u8aEq(
+18 -33
View File
@@ -5,41 +5,26 @@ import type { HexString } from '../types';
import { u8aToU8a } from './toU8a';
interface Constructor<T extends Uint32Array | Uint16Array > {
BYTES_PER_ELEMENT: number;
new(...args: unknown[]): T;
}
// Creates a Uint{16, 32}Array, ensuring that the alignment is correct
function createArray <T extends Uint32Array | Uint16Array> (Clazz: Constructor<T>, value: Uint8Array): T {
// The byteOffset needs to match the size of the data, i.e. for Uint32 it needs to be 4
// NOTE: DataView doesn't have this limitation, but getters are slower
const align = Clazz.BYTES_PER_ELEMENT;
return value.byteOffset % align
? new Clazz(value.buffer.slice(value.byteOffset), 0, value.length / align)
: new Clazz(value.buffer, value.byteOffset, value.length / align);
}
function equalsArray <T extends Uint32Array | Uint16Array | Uint8Array> (a: T, b: T): boolean {
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function equals (a: Uint8Array, b: Uint8Array): boolean {
if (a.length === b.length) {
// NOTE: We don't do BigUint64Array, at this point it is significantly slower
return a.length % Uint32Array.BYTES_PER_ELEMENT
? a.length % Uint16Array.BYTES_PER_ELEMENT
? equalsArray(a, b)
: equalsArray(createArray(Uint16Array, a), createArray(Uint16Array, b))
: equalsArray(createArray(Uint32Array, a), createArray(Uint32Array, b));
const dvA = new DataView(a.buffer, a.byteOffset);
const dvB = new DataView(b.buffer, b.byteOffset);
const mod = a.length % 4;
const length = a.length - mod;
for (let i = 0; i < length; i += 4) {
if (dvA.getUint32(i) !== dvB.getUint32(i)) {
return false;
}
}
for (let i = length; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
return false;
+6 -11
View File
@@ -5,24 +5,19 @@ import type { HexString } from '../types';
import { U8_TO_HEX, U16_TO_HEX } from '../hex/alphabet';
const U16_SIZE = Uint16Array.BYTES_PER_ELEMENT;
/** @internal */
function hex (value: Uint8Array): string {
const mod = value.length % U16_SIZE;
// We need alignment of the Uint16Array, see u8aEq for further details
const u16 = value.byteOffset % U16_SIZE
? new Uint16Array(value.buffer.slice(value.byteOffset), 0, (value.length - mod) / U16_SIZE)
: new Uint16Array(value.buffer, value.byteOffset, (value.length - mod) / U16_SIZE);
const mod = value.length % 2;
const length = value.length - mod;
const dv = new DataView(value.buffer, value.byteOffset);
let result = '';
for (let i = 0; i < u16.length; i++) {
result += U16_TO_HEX[u16[i]];
for (let i = 0; i < length; i += 2) {
result += U16_TO_HEX[dv.getUint16(i)];
}
if (mod) {
result += U8_TO_HEX[value[value.length - 1]];
result += U8_TO_HEX[dv.getUint8(length)];
}
return result;