From f67e7d6262514cfa3f623f0796789593aa404ddb Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Sun, 25 Oct 2026 10:20:03 +0100 Subject: [PATCH] perf(reflect): cache derived types and skip repeated checks reflect.New interned the pointer type of its argument again on every call, computing its identity key and merging its method signatures. PointerTo and SliceOf now cache their result per element type, and internType returns a canonical type as it is. A zero value of a struct type without a registered constructor got a new class each time, so no two values shared a shape. The class is now made once per type, and, like a generated struct class, its constructor makes a zero value. Value.clone ran the argument checks of the constructor again; it now copies the fields of the Value. Signed-off-by: Randall C. O'Reilly --- gs/reflect/type.ts | 45 +++++++++++++++++++++++++++++++++++---------- gs/reflect/value.ts | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/gs/reflect/type.ts b/gs/reflect/type.ts index ed71e4079..856a0eb88 100644 --- a/gs/reflect/type.ts +++ b/gs/reflect/type.ts @@ -300,6 +300,10 @@ function convertValue(x: ReflectValue, ft: Type, t: Type): ReflectValue { } function internType(t: Type): Type { + // A canonical type interns to itself, so it needs no identity key. + if (internedTypes.has(t)) { + return t + } const key = typeIdentityKey(t) $.markReflectTypeIdentity(t, key) const existing = canonicalTypes.get(key) @@ -309,9 +313,13 @@ function internType(t: Type): Type { return existing } canonicalTypes.set(key, t) + internedTypes.add(t) return t } +// internedTypes holds the canonical types, which internType returned. +const internedTypes = new WeakSet() + function mergeTypeMetadata(target: Type, source: Type): void { if (target instanceof BasicType && source instanceof BasicType) { target.mergeMethodSignatures(source.methodSignatures()) @@ -805,14 +813,15 @@ export class Value { } public clone(): Value { - const cloned = new Value( - this._value, - this._type, - this._parentVarRef, - this._parentStruct, - this._fieldName, - this._interfaceValue, - ) + // The fields of a constructed Value are already normalized, so the copy + // takes them as they are rather than checking them again. + const cloned = new Value() + cloned._value = this._value + cloned._type = this._type + cloned._parentVarRef = this._parentVarRef + cloned._parentStruct = this._parentStruct + cloned._fieldName = this._fieldName + cloned._interfaceValue = this._interfaceValue cloned._addressable = this._addressable return cloned } @@ -5575,13 +5584,29 @@ export function ArrayOf(length: number, elem: Type): Type { return internType(new ArrayType(elem, length)) } +// sliceTypes and pointerTypes cache the slice and pointer type of each +// type, which SliceOf and PointerTo would otherwise intern again on every +// call, as reflect.New does. +const sliceTypes = new WeakMap() +const pointerTypes = new WeakMap() + export function SliceOf(t: Type): Type { - return internType(new SliceType(t)) + let st = sliceTypes.get(t) + if (st === undefined) { + st = internType(new SliceType(t)) + sliceTypes.set(t, st) + } + return st } export function PointerTo(t: Type | null): Type | null { if (t === null) return null - return internType(new PointerType(t)) + let pt = pointerTypes.get(t) + if (pt === undefined) { + pt = internType(new PointerType(t)) + pointerTypes.set(t, pt) + } + return pt } export function PtrTo(t: Type | null): Type | null { diff --git a/gs/reflect/value.ts b/gs/reflect/value.ts index 07bd90723..c829e0d38 100644 --- a/gs/reflect/value.ts +++ b/gs/reflect/value.ts @@ -109,22 +109,32 @@ function newStructPointer(typ: Type): ReflectValue { // newStructValue returns a new zero struct of the type typ, which has no // registered constructor, such as an anonymous struct type. function newStructValue(typ: Type): ReflectValue { - type StructValueInstance = Record + const StructValue = structValueClass(typ) + return new StructValue() as ReflectValue +} - // The fields are own properties of the struct object, starting at zero. - const initFields = (value: StructValueInstance): void => { - for (let i = 0; i < typ.NumField(); i++) { - const field = typ.Field(i) - const key = structFieldStorageKey(typ, i) - value[key] = zeroReflectValue(field.Type!) - } +type StructValueInstance = Record + +// structValueClasses caches the class of the values of each struct type +// without a registered constructor, so its values share one shape. +const structValueClasses = new WeakMap StructValueInstance>() + +// structValueClass returns the class of the values of the struct type typ. +// Like a generated struct class, its constructor makes a zero value. +function structValueClass(typ: Type): new () => StructValueInstance { + const cached = structValueClasses.get(typ) + if (cached !== undefined) { + return cached } // A clone copies each field. const StructValue = class { + constructor() { + initStructFields(typ, this as unknown as StructValueInstance) + } + public clone(): unknown { const cloned = new StructValue() as unknown as StructValueInstance - initFields(cloned) for (const key of Object.keys(this)) { cloned[key] = (this as unknown as StructValueInstance)[key] } @@ -135,10 +145,19 @@ function newStructValue(typ: Type): ReflectValue { Object.defineProperty(StructValue, '__typeInfo', { value: typeInfoFromReflectType(typ), }) + const ctor = StructValue as unknown as new () => StructValueInstance + structValueClasses.set(typ, ctor) + return ctor +} - const value = new StructValue() as unknown as StructValueInstance - initFields(value) - return value as ReflectValue +// initStructFields sets the fields of the struct value of type typ to zero, +// as own properties of the struct object. +function initStructFields(typ: Type, value: StructValueInstance): void { + for (let i = 0; i < typ.NumField(); i++) { + const field = typ.Field(i) + const key = structFieldStorageKey(typ, i) + value[key] = zeroReflectValue(field.Type!) + } } // Copy copies the contents of src to dst until either dst has been filled