Performance optimizations (#1181)

* Performance optimizations

* Use slice internally to u8aEq

* Adjust

* formatting

* subarray

* Fixes

* Use DataView

* With alignment

* Use Float64Array when available

* Skip performance

* Renames

* Alignment comment

* Constants

* Use BYTES_PER_ELEMENT

* Adjust
This commit is contained in:
Jaco
2021-11-14 17:21:07 +02:00
committed by GitHub
parent 23fa2b90a8
commit 20821aad40
12 changed files with 206 additions and 23 deletions
+2
View File
@@ -8,6 +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
## 7.8.2 Nov 7, 2021
+2 -2
View File
@@ -27,9 +27,9 @@ export function compactFromU8a (_input: U8aLike): [number, BN] {
if (flag === 0b00) {
return [1, new BN(input[0]).ishrn(2)];
} else if (flag === 0b01) {
return [2, u8aToBn(input.slice(0, 2), true).ishrn(2)];
return [2, u8aToBn(input.subarray(0, 2), true).ishrn(2)];
} else if (flag === 0b10) {
return [4, u8aToBn(input.slice(0, 4), true).ishrn(2)];
return [4, u8aToBn(input.subarray(0, 4), true).ishrn(2)];
}
const offset = 1 + (
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2017-2021 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
const U8_TO_HEX = new Array<string>(256);
const U16_TO_HEX = new Array<string>(256 * 256);
const HEX_TO_U8: Record<string, number> = {};
const HEX_TO_U16: Record<string, number> = {};
for (let n = 0; n < 256; n++) {
const hex = n.toString(16).padStart(2, '0');
U8_TO_HEX[n] = hex;
HEX_TO_U8[hex] = 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;
U16_TO_HEX[n] = hex;
HEX_TO_U16[hex] = n;
}
}
export { HEX_TO_U16, HEX_TO_U8, U16_TO_HEX, U8_TO_HEX };
+18 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2017-2021 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { U16_TO_HEX } from './alphabet';
import { hexToU8a } from '.';
describe('hexToU8a', (): void => {
@@ -20,7 +21,7 @@ describe('hexToU8a', (): void => {
it('returns a Uint8Array with the correct values (bitLength > provided)', (): void => {
expect(
hexToU8a('0x80000a', 32)
hexToU8a('0x80000A', 32)
).toEqual(
new Uint8Array([0, 128, 0, 10])
);
@@ -39,4 +40,20 @@ describe('hexToU8a', (): void => {
(): Uint8Array => hexToU8a('notahex')
).toThrow(/hex value to convert/);
});
it.skip('performance', (): void => {
let a = '0x';
for (let i = 0; i < 8192; i++) {
a += U16_TO_HEX[i];
}
console.time('hexToU8a:performance');
for (let i = 0; i < 8192; i++) {
hexToU8a(a);
}
console.timeEnd('hexToU8a:performance');
});
});
+5 -2
View File
@@ -5,6 +5,7 @@ import type { HexString } from '../types';
import { assert } from '../assert';
import { isHex } from '../is/hex';
import { HEX_TO_U8 } from './alphabet';
import { hexStripPrefix } from './stripPrefix';
/**
@@ -29,7 +30,7 @@ export function hexToU8a (_value?: HexString | string | null, bitLength = -1): U
assert(isHex(_value), () => `Expected hex value to convert, found '${_value}'`);
const value = hexStripPrefix(_value);
const value = hexStripPrefix(_value).toLowerCase();
const valLength = value.length / 2;
const resultLength = Math.ceil(
bitLength === -1
@@ -40,7 +41,9 @@ export function hexToU8a (_value?: HexString | string | null, bitLength = -1): U
const offset = Math.max(0, resultLength - valLength);
for (let index = 0; index < resultLength; index++) {
result[index + offset] = parseInt(value.substr(index * 2, 2), 16);
// TODO With some magic we can use the HEX_TO_U16 lookups here as well
// (and then finally HEX_TO_U8 for the overflows)
result[index + offset] = HEX_TO_U8[value.substr(index * 2, 2)];
}
return result;
+19 -2
View File
@@ -1,7 +1,8 @@
// Copyright 2017-2021 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { hexToU8a } from '.';
import { U16_TO_HEX } from './alphabet';
import { hexToU8a } from './toU8aBuffer';
describe('hexToU8a', (): void => {
it('returns an empty Uint8Array when null provided', (): void => {
@@ -20,7 +21,7 @@ describe('hexToU8a', (): void => {
it('returns a Uint8Array with the correct values (bitLength > provided)', (): void => {
expect(
hexToU8a('0x80000a', 32)
hexToU8a('0x80000A', 32)
).toEqual(
new Uint8Array([0, 128, 0, 10])
);
@@ -39,4 +40,20 @@ describe('hexToU8a', (): void => {
(): Uint8Array => hexToU8a('notahex')
).toThrow(/hex value to convert/);
});
it.skip('performance', (): void => {
let a = '0x';
for (let i = 0; i < 8192; i++) {
a += U16_TO_HEX[i];
}
console.time('hexToU8a:performance');
for (let i = 0; i < 8192; i++) {
hexToU8a(a);
}
console.timeEnd('hexToU8a:performance');
});
});
+6 -5
View File
@@ -22,15 +22,16 @@ import { hexStripPrefix } from './stripPrefix';
* hexToU8a('0x80001f', 32); // Uint8Array([0x00, 0x80, 0x00, 0x1f])
* ```
*/
export function hexToU8a (value?: HexString | string | null, bitLength = -1): Uint8Array {
if (!value) {
export function hexToU8a (_value?: HexString | string | null, bitLength = -1): Uint8Array {
if (!_value) {
return new Uint8Array();
}
assert(isHex(value), () => `Expected hex value to convert, found '${value}'`);
assert(isHex(_value), () => `Expected hex value to convert, found '${_value}'`);
const buf = Buffer.from(hexStripPrefix(value), 'hex');
const valLength = buf.length / 2;
const value = hexStripPrefix(_value);
const buf = Buffer.from(value, 'hex');
const valLength = value.length / 2;
const resultLength = Math.ceil(
bitLength === -1
? valLength
+28
View File
@@ -39,4 +39,32 @@ describe('u8aEq', (): void => {
)
).toEqual(true);
});
it('works on aubarray values', (): void => {
const a = new Uint8Array(16);
for (let i = 0; i < a.length; i++) {
a[i] = i;
}
expect(
u8aEq(a.subarray(0, 5), a.subarray(0, 5))
).toEqual(true);
});
it.skip('performance', (): void => {
const a = new Uint8Array(32768);
for (let i = 0; i < a.length; i++) {
a[i] = i % 256;
}
console.time('u8aEq:performance');
for (let i = 0; i < 65536; i++) {
u8aEq(a, a);
}
console.timeEnd('u8aEq:performance');
});
});
+50 -2
View File
@@ -3,11 +3,59 @@
import type { HexString } from '../types';
import { u8aCmp } from './cmp';
import { u8aToU8a } from './toU8a';
interface Constructor<T extends BigUint64Array | Uint32Array | Uint16Array | Uint8Array> {
BYTES_PER_ELEMENT: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new(...args: any[]): T;
}
// React-native does not have BigUint64Array, check others as well
const U64_BYTES = typeof BigUint64Array !== 'undefined'
? BigUint64Array.BYTES_PER_ELEMENT
: 0;
const U32_BYTES = typeof Uint32Array !== 'undefined'
? Uint32Array.BYTES_PER_ELEMENT
: 0;
const U16_BYTES = typeof Uint16Array !== 'undefined'
? Uint16Array.BYTES_PER_ELEMENT
: 0;
// Creates a Uint8Array, ensuring that the alignment is correct
function createArray <T extends BigUint64Array | Uint32Array | Uint16Array | Uint8Array> (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 BigUint64Array | 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 {
return (a.length === b.length) && (u8aCmp(a, b) === 0);
if (a.length === b.length) {
return U64_BYTES && (a.length % U64_BYTES)
? U32_BYTES && (a.length % U32_BYTES)
? U16_BYTES && (a.length % U16_BYTES)
? equalsArray(a, b)
: equalsArray(createArray(Uint16Array, a), createArray(Uint16Array, b))
: equalsArray(createArray(Uint32Array, a), createArray(Uint32Array, b))
: equalsArray(createArray(BigUint64Array, a), createArray(BigUint64Array, b));
}
return false;
}
/**
+16
View File
@@ -57,4 +57,20 @@ describe('u8aToHex', (): void => {
)
).toEqual('0x8000…0c0d');
});
it.skip('performance', (): void => {
const a = new Uint8Array(32768);
for (let i = 0; i < a.length; i++) {
a[i] = i % 256;
}
console.time('u8aToHex:performance');
for (let i = 0; i < 65536; i++) {
u8aToHex(a);
}
console.timeEnd('u8aToHex:performance');
});
});
+17 -9
View File
@@ -1,23 +1,31 @@
// Copyright 2017-2021 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexDigit, HexString } from '../types';
import type { HexString } from '../types';
import { arrayRange } from '../array';
import { U8_TO_HEX, U16_TO_HEX } from '../hex/alphabet';
type HexByte = `${HexDigit}${HexDigit}`;
const ALPHABET = arrayRange(256).map((n) => n.toString(16).padStart(2, '0') as HexByte);
const U16_SIZE = Uint16Array.BYTES_PER_ELEMENT;
/** @internal */
function hex (value: Uint8Array): string {
const result = new Array<HexByte>(value.length);
const mod = value.length % U16_SIZE;
for (let i = 0; i < value.length; i++) {
result[i] = ALPHABET[value[i]];
// 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);
let result = '';
for (let i = 0; i < u16.length; i++) {
result += U16_TO_HEX[u16[i]];
}
return result.join('');
if (mod) {
result += U8_TO_HEX[value[value.length - 1]];
}
return result;
}
/**
+16
View File
@@ -57,4 +57,20 @@ describe('u8aToHex', (): void => {
)
).toEqual('0x8000…0c0d');
});
it.skip('performance', (): void => {
const a = new Uint8Array(32768);
for (let i = 0; i < a.length; i++) {
a[i] = i % 256;
}
console.time('u8aToHex:performance');
for (let i = 0; i < 65536; i++) {
u8aToHex(a);
}
console.timeEnd('u8aToHex:performance');
});
});