Performance tweaks (#4143)

* Avoid unnecessary work in `filterEvents`

* Memoize computed extrinsic hash

* Optimize sanitize by reusing precompiled regular expressions

* Memoize generation of property names in `Enum` and `Set`

* Memoize sanitizing
This commit is contained in:
Nazar Mokrynskyi
2021-11-06 09:25:54 +02:00
committed by GitHub
parent 8402013e97
commit 72c7a0a34b
6 changed files with 167 additions and 40 deletions
+38 -2
View File
@@ -155,6 +155,42 @@ function decodeEnum (registry: Registry, def: TypesDef, value?: any, index?: num
return decodeFromValue(registry, def, value);
}
const camelCaseMap = new Map<string, string>();
function memoizedStringCamelCase (value: string): string {
{
const camelCaseValue = camelCaseMap.get(value);
if (camelCaseValue) {
return camelCaseValue;
}
}
const camelCaseValue = stringCamelCase(value);
camelCaseMap.set(value, camelCaseValue);
return camelCaseValue;
}
const propertySuffixMap = new Map<string, string>();
function memoizedPropertySuffix (value: string): string {
{
const suffix = propertySuffixMap.get(value);
if (suffix) {
return suffix;
}
}
const suffix = stringUpperFirst(stringCamelCase(value.replace(' ', '_')));
propertySuffixMap.set(value, suffix);
return suffix;
}
/**
* @name Enum
* @description
@@ -206,7 +242,7 @@ export class Enum implements IEnum {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const name = stringUpperFirst(stringCamelCase(key.replace(' ', '_')));
const name = memoizedPropertySuffix(key);
const askey = `as${name}`;
const iskey = `is${name}`;
@@ -344,7 +380,7 @@ export class Enum implements IEnum {
public toJSON (): AnyJson {
return this.#isBasic
? this.type
: { [stringCamelCase(this.type)]: this.#raw.toJSON() };
: { [memoizedStringCamelCase(this.type)]: this.#raw.toJSON() };
}
/**
+19 -1
View File
@@ -80,6 +80,24 @@ function decodeSet (setValues: SetValues, value: string[] | Set<string> | Uint8A
return decodeSetNumber(setValues, value);
}
const iskeyMap = new Map<string, string>();
function memoizedIskey (value: string): string {
{
const iskey = iskeyMap.get(value);
if (iskey) {
return iskey;
}
}
const iskey = `is${stringUpperFirst(stringCamelCase(value))}`;
iskeyMap.set(value, iskey);
return iskey;
}
/**
* @name Set
* @description
@@ -113,7 +131,7 @@ export class CodecSet extends Set<string> implements ISet<string> {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
defineProperty(this, `is${stringUpperFirst(stringCamelCase(key))}`, () => this.strings.includes(key));
defineProperty(this, memoizedIskey(key), () => this.strings.includes(key));
}
}
};
+61 -20
View File
@@ -68,14 +68,18 @@ export function findClosing (value: string, start: number): number {
}
export function alias (src: string, dest: string, withChecks = true): Mapper {
const from = new RegExp(`(^${src}|${BOX_PRECEDING.map((box) => `\\${box}${src}`).join('|')})`, 'g');
const to = (src: string): string => {
from.lastIndex = 0;
return withChecks && BOX_PRECEDING.includes(src[0])
? `${src[0]}${dest}`
: dest;
};
return (value: string): string =>
value.replace(
new RegExp(`(^${src}|${BOX_PRECEDING.map((box) => `\\${box}${src}`).join('|')})`, 'g'),
(src): string =>
withChecks && BOX_PRECEDING.includes(src[0])
? `${src[0]}${dest}`
: dest
);
value.replace(from, to);
}
export function cleanupCompact (): Mapper {
@@ -95,12 +99,18 @@ export function cleanupCompact (): Mapper {
}
export function flattenSingleTuple (): Mapper {
return (value: string) =>
value
const from1 = /,\)/g;
const from2 = /\(([^,]+)\)/;
return (value: string) => {
from1.lastIndex = 0;
return value
// tuples may have trailing commas, e.g. (u32, BlockNumber, )
.replace(/,\)/g, ')')
.replace(from1, ')')
// change (u32) -> u32
.replace(/\(([^,]+)\)/, '$1');
.replace(from2, '$1');
};
}
function replaceTagWith (value: string, matcher: string, replacer: (inner: string) => string): string {
@@ -204,20 +214,35 @@ export function removePairOf (): Mapper {
// remove the type traits
export function removeTraits (): Mapper {
return (value: string): string =>
value
const from1 = /\s/g;
const from2 = /(T|Self)::/g;
const from3 = /<(T|Self)asTrait>::/g;
const from4 = /<Tas[a-z]+::Trait>::/g;
const from5 = /<LookupasStaticLookup>/g;
const from6 = /::Type/g;
return (value: string): string => {
from1.lastIndex = 0;
from2.lastIndex = 0;
from3.lastIndex = 0;
from4.lastIndex = 0;
from5.lastIndex = 0;
from6.lastIndex = 0;
return value
// remove all whitespaces
.replace(/\s/g, '')
.replace(from1, '')
// anything `T::<type>` to end up as `<type>`
.replace(/(T|Self)::/g, '')
.replace(from2, '')
// replace `<T as Trait>::` (whitespaces were removed above)
.replace(/<(T|Self)asTrait>::/g, '')
.replace(from3, '')
// replace `<T as something::Trait>::` (whitespaces were removed above)
.replace(/<Tas[a-z]+::Trait>::/g, '')
.replace(from4, '')
// replace <Lookup as StaticLookup>
.replace(/<LookupasStaticLookup>/g, 'Lookup')
.replace(from5, 'Lookup')
// replace `<...>::Type`
.replace(/::Type/g, '');
.replace(from6, '');
};
}
// remove wrapping values, i.e. Box<Proposal> -> Proposal
@@ -228,13 +253,29 @@ export function removeWrap (check: string): Mapper {
replaceTagWith(value, check, replacer);
}
const sanitizeMap = new Map<string, string>();
// eslint-disable-next-line @typescript-eslint/ban-types
export function sanitize (value: String | string, options?: SanitizeOptions): string {
let result = value.toString();
if (!options) {
const memoized = sanitizeMap.get(result);
if (memoized) {
return memoized;
}
}
for (let i = 0; i < mappings.length; i++) {
result = mappings[i](result, options);
}
return result.trim();
result = result.trim();
if (!options) {
sanitizeMap.set(value.toString(), result);
}
return result;
}
+17
View File
@@ -13,6 +13,7 @@ import { assert, compactAddLength, compactFromU8a, isHex, isU8a, u8aConcat, u8aT
import { Base } from '../codec/Base';
import { Compact } from '../codec/Compact';
import { CodecHash } from '../interfaces/runtime';
import { BIT_SIGNED, BIT_UNSIGNED, DEFAULT_VERSION, UNMASK_VERSION } from './constants';
interface CreateOptions {
@@ -36,6 +37,8 @@ const VERSIONS = [
export { EXTRINSIC_VERSION as LATEST_EXTRINSIC_VERSION } from './v4/Extrinsic';
abstract class ExtrinsicBase<A extends AnyTuple> extends Base<ExtrinsicVx | ExtrinsicUnknown> {
protected hashCache?: CodecHash;
/**
* @description The arguments passed to for the call, exposes args so it is compatible with [[Call]]
*/
@@ -78,6 +81,17 @@ abstract class ExtrinsicBase<A extends AnyTuple> extends Base<ExtrinsicVx | Extr
return this.toU8a().length;
}
/**
* @description returns a hash of the contents
*/
public override get hash (): CodecHash {
if (!this.hashCache) {
this.hashCache = super.hash;
}
return this.hashCache;
}
/**
* @description `true` id the extrinsic is signed
*/
@@ -219,6 +233,7 @@ export class GenericExtrinsic<A extends AnyTuple = AnyTuple> extends ExtrinsicBa
*/
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsic<A> {
(this._raw as ExtrinsicVx).addSignature(signer, signature, payload);
this.hashCache = undefined;
return this;
}
@@ -228,6 +243,7 @@ export class GenericExtrinsic<A extends AnyTuple = AnyTuple> extends ExtrinsicBa
*/
public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsic<A> {
(this._raw as ExtrinsicVx).sign(account, options);
this.hashCache = undefined;
return this;
}
@@ -237,6 +253,7 @@ export class GenericExtrinsic<A extends AnyTuple = AnyTuple> extends ExtrinsicBa
*/
public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsic<A> {
(this._raw as ExtrinsicVx).signFake(signer, options);
this.hashCache = undefined;
return this;
}
@@ -10,6 +10,7 @@ import type { ExtrinsicOptions } from '../types';
import { isU8a } from '@polkadot/util';
import { Struct } from '../../codec/Struct';
import { CodecHash } from '../../interfaces/runtime';
export const EXTRINSIC_VERSION = 4;
@@ -24,6 +25,8 @@ export interface ExtrinsicValueV4 {
* The third generation of compact extrinsics
*/
export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
#hashCache?: CodecHash;
constructor (registry: Registry, value?: Uint8Array | ExtrinsicValueV4 | Call, { isSigned }: Partial<ExtrinsicOptions> = {}) {
super(registry, {
signature: 'ExtrinsicSignatureV4',
@@ -59,6 +62,17 @@ export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
return this.toU8a().length;
}
/**
* @description returns a hash of the contents
*/
public override get hash (): CodecHash {
if (!this.#hashCache) {
this.#hashCache = super.hash;
}
return this.#hashCache;
}
/**
* @description The [[Call]] this extrinsic wraps
*/
@@ -85,6 +99,7 @@ export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
*/
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsicV4 {
this.signature.addSignature(signer, signature, payload);
this.#hashCache = undefined;
return this;
}
@@ -94,6 +109,7 @@ export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
*/
public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsicV4 {
this.signature.sign(this.method, account, options);
this.#hashCache = undefined;
return this;
}
@@ -103,6 +119,7 @@ export class GenericExtrinsicV4 extends Struct implements IExtrinsicImpl {
*/
public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsicV4 {
this.signature.signFake(this.method, signer, options);
this.#hashCache = undefined;
return this;
}