Skip to content

Commit

Permalink
Improve from Binary/Json/Partial performance by roughly 30% (#582)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcready authored Dec 4, 2023
1 parent 63766cd commit d1e441a
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 38 deletions.
75 changes: 39 additions & 36 deletions packages/plugin/src/message-type-extensions/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ export class Create implements CustomMethodGenerator {
source,
descriptor,

// const message = { boolField: false, ... };
this.makeMessageVariable(source, descriptor),
// const message = globalThis.Object.create(this.messagePrototype);
this.makeMessageVariable(),

// Object.defineProperty(message, MESSAGE_TYPE, {enumerable: false, value: this});
this.makeDefineMessageTypeSymbolProperty(source),
// message.boolField = false;
// message.repeatedField = [];
// message.mapField = {};
// ...
...this.makeMessagePropertyAssignments(source, descriptor),

// if (value !== undefined)
// reflectionMergePartial<ScalarValuesMessage>(message, value, this);
Expand Down Expand Up @@ -73,52 +76,52 @@ export class Create implements CustomMethodGenerator {
}


makeMessageVariable(source: TypescriptFile, descriptor: DescriptorProto) {
let messageType = this.interpreter.getMessageType(descriptor);
let defaultMessage = messageType.create();
makeMessageVariable() {
return ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList(
[ts.createVariableDeclaration(
ts.createIdentifier("message"),
undefined,
typescriptLiteralFromValue(defaultMessage)
ts.createCall(
ts.createPropertyAccess(
ts.createPropertyAccess(
ts.createIdentifier("globalThis"),
ts.createIdentifier("Object")
),
ts.createIdentifier("create")
),
undefined,
[
ts.createNonNullExpression(
ts.createPropertyAccess(
ts.createThis(),
ts.createIdentifier("messagePrototype")
)
)
]
)
)],
ts.NodeFlags.Const
)
);
}


makeDefineMessageTypeSymbolProperty(source: TypescriptFile) {
const MESSAGE_TYPE = this.imports.name(source, 'MESSAGE_TYPE', this.options.runtimeImportPath);

return ts.createExpressionStatement(ts.createCall(
ts.createPropertyAccess(
ts.createPropertyAccess(
ts.createIdentifier("globalThis"),
ts.createIdentifier("Object")
),
ts.createIdentifier("defineProperty")
),
undefined,
[
ts.createIdentifier("message"),
ts.createIdentifier(MESSAGE_TYPE),
ts.createObjectLiteral(
[
ts.createPropertyAssignment(
ts.createIdentifier("enumerable"),
ts.createFalse()
),
ts.createPropertyAssignment(
ts.createIdentifier("value"),
ts.createThis()
)
],
false
makeMessagePropertyAssignments(source: TypescriptFile, descriptor: DescriptorProto) {
let messageType = this.interpreter.getMessageType(descriptor);
let defaultMessage = messageType.create();
return Object.entries(defaultMessage).map(([key, value]): ts.ExpressionStatement => (
ts.createExpressionStatement(
ts.createBinary(
ts.createPropertyAccess(
ts.createIdentifier("message"),
ts.createIdentifier(key)
),
ts.createToken(ts.SyntaxKind.EqualsToken),
typescriptLiteralFromValue(value)
)
]
)
));
}

Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/src/message-type-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export interface IMessageType<T extends object> extends MessageInfo {
*/
readonly options: { [extensionName: string]: JsonValue };

/**
* Contains the prototype for messages returned by create() which
* includes the `MESSAGE_TYPE` symbol pointing back to `this`.
*/
readonly messagePrototype?: Readonly<{}>;


/**
* Create a new message with default values.
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/message-type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {IMessageType, PartialMessage} from "./message-type-contract";
import {MESSAGE_TYPE} from "./message-type-contract";
import type {FieldInfo, PartialFieldInfo} from "./reflection-info";
import {normalizeFieldInfo} from "./reflection-info";
import {ReflectionTypeCheck} from "./reflection-type-check";
Expand Down Expand Up @@ -49,6 +50,11 @@ export class MessageType<T extends object> implements IMessageType<T> {
*/
readonly options: JsonOptionsMap;

/**
* Contains the prototype for messages returned by create() which
* includes the `MESSAGE_TYPE` symbol pointing back to `this`.
*/
readonly messagePrototype: Readonly<{}> | undefined;

protected readonly defaultCheckDepth = 16;
protected readonly refTypeCheck: ReflectionTypeCheck;
Expand All @@ -61,6 +67,7 @@ export class MessageType<T extends object> implements IMessageType<T> {
this.typeName = name;
this.fields = fields.map(normalizeFieldInfo);
this.options = options ?? {};
this.messagePrototype = Object.defineProperty({}, MESSAGE_TYPE, { value: this });
this.refTypeCheck = new ReflectionTypeCheck(this);
this.refJsonReader = new ReflectionJsonReader(this);
this.refJsonWriter = new ReflectionJsonWriter(this);
Expand Down
15 changes: 13 additions & 2 deletions packages/runtime/src/reflection-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ import {MESSAGE_TYPE} from './message-type-contract';
* information.
*/
export function reflectionCreate<T extends object>(type: IMessageType<T>): T {
const msg: UnknownMessage = {};
Object.defineProperty(msg, MESSAGE_TYPE, {enumerable: false, value: type});
/**
* This ternary can be removed in the next major version.
* The `Object.create()` code path utilizes a new `messagePrototype`
* property on the `IMessageType` which has this same `MESSAGE_TYPE`
* non-enumerable property on it. Doing it this way means that we only
* pay the cost of `Object.defineProperty()` once per `IMessageType`
* class of once per "instance". The falsy code path is only provided
* for backwards compatibility in cases where the runtime library is
* updated without also updating the generated code.
*/
const msg: UnknownMessage = type.messagePrototype
? Object.create(type.messagePrototype)
: Object.defineProperty({}, MESSAGE_TYPE, {value: type});
for (let field of type.fields) {
let name = field.localName;
if (field.opt)
Expand Down

0 comments on commit d1e441a

Please sign in to comment.