From 72c7a0a34b4c625bdd2878e2d440e57f88210762 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Sat, 6 Nov 2021 08:25:54 +0100 Subject: [PATCH] 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 --- packages/api/src/util/filterEvents.ts | 32 ++++---- packages/types/src/codec/Enum.ts | 40 +++++++++- packages/types/src/codec/Set.ts | 20 ++++- packages/types/src/create/sanitize.ts | 81 +++++++++++++++----- packages/types/src/extrinsic/Extrinsic.ts | 17 ++++ packages/types/src/extrinsic/v4/Extrinsic.ts | 17 ++++ 6 files changed, 167 insertions(+), 40 deletions(-) diff --git a/packages/api/src/util/filterEvents.ts b/packages/api/src/util/filterEvents.ts index 34c50071bd..eab135452e 100644 --- a/packages/api/src/util/filterEvents.ts +++ b/packages/api/src/util/filterEvents.ts @@ -7,24 +7,22 @@ import { l } from './logging'; export function filterEvents (extHash: H256, { block: { extrinsics, header } }: SignedBlock, allEvents: EventRecord[], status: ExtrinsicStatus): EventRecord[] | undefined { // extrinsics to hashes - const myHash = extHash.toHex(); - const allHashes = extrinsics.map((ext): string => ext.hash.toHex()); - - // find the index of our extrinsic in the block - const index = allHashes.indexOf(myHash); - - // if we do get the block after finalized, it _should_ be there - if (index === -1) { - // only warn on filtering with isInBlock (finalization finalizes after) - if (status.isInBlock) { - l.warn(`block ${header.hash.toHex()}: Unable to find extrinsic ${myHash} inside ${allHashes.join(', ')}`); + for (const [index, extrinsic] of extrinsics.entries()) { + if (extrinsic.hash.eq(extHash)) { + return allEvents.filter(({ phase }) => + // only ApplyExtrinsic has the extrinsic index + phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(index) + ); } - - return; } - return allEvents.filter(({ phase }) => - // only ApplyExtrinsic has the extrinsic index - phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(index) - ); + // if we do get the block after finalized, it _should_ be there + // only warn on filtering with isInBlock (finalization finalizes after) + if (status.isInBlock) { + const allHashes = extrinsics.map((ext): string => ext.hash.toHex()); + + l.warn(`block ${header.hash.toHex()}: Unable to find extrinsic ${extHash.toHex()} inside ${allHashes.join(', ')}`); + } + + return undefined; } diff --git a/packages/types/src/codec/Enum.ts b/packages/types/src/codec/Enum.ts index 3ded1661d5..5367cfa353 100644 --- a/packages/types/src/codec/Enum.ts +++ b/packages/types/src/codec/Enum.ts @@ -155,6 +155,42 @@ function decodeEnum (registry: Registry, def: TypesDef, value?: any, index?: num return decodeFromValue(registry, def, value); } +const camelCaseMap = new Map(); + +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(); + +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() }; } /** diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index d6ee093254..67961667b5 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -80,6 +80,24 @@ function decodeSet (setValues: SetValues, value: string[] | Set | Uint8A return decodeSetNumber(setValues, value); } +const iskeyMap = new Map(); + +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 implements ISet { 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)); } } }; diff --git a/packages/types/src/create/sanitize.ts b/packages/types/src/create/sanitize.ts index 185bfff998..b0065fb437 100644 --- a/packages/types/src/create/sanitize.ts +++ b/packages/types/src/create/sanitize.ts @@ -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 = /::/g; + const from5 = //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::` to end up as `` - .replace(/(T|Self)::/g, '') + .replace(from2, '') // replace `::` (whitespaces were removed above) - .replace(/<(T|Self)asTrait>::/g, '') + .replace(from3, '') // replace `::` (whitespaces were removed above) - .replace(/::/g, '') + .replace(from4, '') // replace - .replace(//g, 'Lookup') + .replace(from5, 'Lookup') // replace `<...>::Type` - .replace(/::Type/g, ''); + .replace(from6, ''); + }; } // remove wrapping values, i.e. Box -> Proposal @@ -228,13 +253,29 @@ export function removeWrap (check: string): Mapper { replaceTagWith(value, check, replacer); } +const sanitizeMap = new Map(); + // 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; } diff --git a/packages/types/src/extrinsic/Extrinsic.ts b/packages/types/src/extrinsic/Extrinsic.ts index 69a6af0616..017e191d4a 100644 --- a/packages/types/src/extrinsic/Extrinsic.ts +++ b/packages/types/src/extrinsic/Extrinsic.ts @@ -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 extends Base { + 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 extends Base extends ExtrinsicBa */ public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsic { (this._raw as ExtrinsicVx).addSignature(signer, signature, payload); + this.hashCache = undefined; return this; } @@ -228,6 +243,7 @@ export class GenericExtrinsic extends ExtrinsicBa */ public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsic { (this._raw as ExtrinsicVx).sign(account, options); + this.hashCache = undefined; return this; } @@ -237,6 +253,7 @@ export class GenericExtrinsic extends ExtrinsicBa */ public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsic { (this._raw as ExtrinsicVx).signFake(signer, options); + this.hashCache = undefined; return this; } diff --git a/packages/types/src/extrinsic/v4/Extrinsic.ts b/packages/types/src/extrinsic/v4/Extrinsic.ts index c8ef604946..239bc7e072 100644 --- a/packages/types/src/extrinsic/v4/Extrinsic.ts +++ b/packages/types/src/extrinsic/v4/Extrinsic.ts @@ -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 = {}) { 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; }