Skip to content

Commit d1e441a

Browse files
authored
Improve from Binary/Json/Partial performance by roughly 30% (#582)
1 parent 63766cd commit d1e441a

File tree

4 files changed

+65
-38
lines changed

4 files changed

+65
-38
lines changed

packages/plugin/src/message-type-extensions/create.ts

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ export class Create implements CustomMethodGenerator {
3434
source,
3535
descriptor,
3636

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

40-
// Object.defineProperty(message, MESSAGE_TYPE, {enumerable: false, value: this});
41-
this.makeDefineMessageTypeSymbolProperty(source),
40+
// message.boolField = false;
41+
// message.repeatedField = [];
42+
// message.mapField = {};
43+
// ...
44+
...this.makeMessagePropertyAssignments(source, descriptor),
4245

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

7578

76-
makeMessageVariable(source: TypescriptFile, descriptor: DescriptorProto) {
77-
let messageType = this.interpreter.getMessageType(descriptor);
78-
let defaultMessage = messageType.create();
79+
makeMessageVariable() {
7980
return ts.createVariableStatement(
8081
undefined,
8182
ts.createVariableDeclarationList(
8283
[ts.createVariableDeclaration(
8384
ts.createIdentifier("message"),
8485
undefined,
85-
typescriptLiteralFromValue(defaultMessage)
86+
ts.createCall(
87+
ts.createPropertyAccess(
88+
ts.createPropertyAccess(
89+
ts.createIdentifier("globalThis"),
90+
ts.createIdentifier("Object")
91+
),
92+
ts.createIdentifier("create")
93+
),
94+
undefined,
95+
[
96+
ts.createNonNullExpression(
97+
ts.createPropertyAccess(
98+
ts.createThis(),
99+
ts.createIdentifier("messagePrototype")
100+
)
101+
)
102+
]
103+
)
86104
)],
87105
ts.NodeFlags.Const
88106
)
89107
);
90108
}
91109

92110

93-
makeDefineMessageTypeSymbolProperty(source: TypescriptFile) {
94-
const MESSAGE_TYPE = this.imports.name(source, 'MESSAGE_TYPE', this.options.runtimeImportPath);
95-
96-
return ts.createExpressionStatement(ts.createCall(
97-
ts.createPropertyAccess(
98-
ts.createPropertyAccess(
99-
ts.createIdentifier("globalThis"),
100-
ts.createIdentifier("Object")
101-
),
102-
ts.createIdentifier("defineProperty")
103-
),
104-
undefined,
105-
[
106-
ts.createIdentifier("message"),
107-
ts.createIdentifier(MESSAGE_TYPE),
108-
ts.createObjectLiteral(
109-
[
110-
ts.createPropertyAssignment(
111-
ts.createIdentifier("enumerable"),
112-
ts.createFalse()
113-
),
114-
ts.createPropertyAssignment(
115-
ts.createIdentifier("value"),
116-
ts.createThis()
117-
)
118-
],
119-
false
111+
makeMessagePropertyAssignments(source: TypescriptFile, descriptor: DescriptorProto) {
112+
let messageType = this.interpreter.getMessageType(descriptor);
113+
let defaultMessage = messageType.create();
114+
return Object.entries(defaultMessage).map(([key, value]): ts.ExpressionStatement => (
115+
ts.createExpressionStatement(
116+
ts.createBinary(
117+
ts.createPropertyAccess(
118+
ts.createIdentifier("message"),
119+
ts.createIdentifier(key)
120+
),
121+
ts.createToken(ts.SyntaxKind.EqualsToken),
122+
typescriptLiteralFromValue(value)
120123
)
121-
]
124+
)
122125
));
123126
}
124127

packages/runtime/src/message-type-contract.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export interface IMessageType<T extends object> extends MessageInfo {
5858
*/
5959
readonly options: { [extensionName: string]: JsonValue };
6060

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

6268
/**
6369
* Create a new message with default values.

packages/runtime/src/message-type.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {IMessageType, PartialMessage} from "./message-type-contract";
2+
import {MESSAGE_TYPE} from "./message-type-contract";
23
import type {FieldInfo, PartialFieldInfo} from "./reflection-info";
34
import {normalizeFieldInfo} from "./reflection-info";
45
import {ReflectionTypeCheck} from "./reflection-type-check";
@@ -49,6 +50,11 @@ export class MessageType<T extends object> implements IMessageType<T> {
4950
*/
5051
readonly options: JsonOptionsMap;
5152

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

5359
protected readonly defaultCheckDepth = 16;
5460
protected readonly refTypeCheck: ReflectionTypeCheck;
@@ -61,6 +67,7 @@ export class MessageType<T extends object> implements IMessageType<T> {
6167
this.typeName = name;
6268
this.fields = fields.map(normalizeFieldInfo);
6369
this.options = options ?? {};
70+
this.messagePrototype = Object.defineProperty({}, MESSAGE_TYPE, { value: this });
6471
this.refTypeCheck = new ReflectionTypeCheck(this);
6572
this.refJsonReader = new ReflectionJsonReader(this);
6673
this.refJsonWriter = new ReflectionJsonWriter(this);

packages/runtime/src/reflection-create.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@ import {MESSAGE_TYPE} from './message-type-contract';
88
* information.
99
*/
1010
export function reflectionCreate<T extends object>(type: IMessageType<T>): T {
11-
const msg: UnknownMessage = {};
12-
Object.defineProperty(msg, MESSAGE_TYPE, {enumerable: false, value: type});
11+
/**
12+
* This ternary can be removed in the next major version.
13+
* The `Object.create()` code path utilizes a new `messagePrototype`
14+
* property on the `IMessageType` which has this same `MESSAGE_TYPE`
15+
* non-enumerable property on it. Doing it this way means that we only
16+
* pay the cost of `Object.defineProperty()` once per `IMessageType`
17+
* class of once per "instance". The falsy code path is only provided
18+
* for backwards compatibility in cases where the runtime library is
19+
* updated without also updating the generated code.
20+
*/
21+
const msg: UnknownMessage = type.messagePrototype
22+
? Object.create(type.messagePrototype)
23+
: Object.defineProperty({}, MESSAGE_TYPE, {value: type});
1324
for (let field of type.fields) {
1425
let name = field.localName;
1526
if (field.opt)

0 commit comments

Comments
 (0)