perf(change_detection): do not check intermediate results
This commit is contained in:
@ -71,7 +71,6 @@ export class ChangeDetectorJITGenerator {
|
||||
return new ${this._typeName}(dispatcher, protos, directiveRecords);
|
||||
}
|
||||
`;
|
||||
|
||||
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos',
|
||||
'directiveRecords', classDefinition)(
|
||||
AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords);
|
||||
@ -171,7 +170,6 @@ export class ChangeDetectorJITGenerator {
|
||||
|
||||
var oldValue = this._names.getFieldName(r.selfIndex);
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
var change = this._names.getChangeName(r.selfIndex);
|
||||
|
||||
var pipe = this._names.getPipeName(r.selfIndex);
|
||||
var cdRef = "this.ref";
|
||||
@ -179,7 +177,7 @@ export class ChangeDetectorJITGenerator {
|
||||
var protoIndex = r.selfIndex - 1;
|
||||
var pipeType = r.name;
|
||||
|
||||
return `
|
||||
var read = `
|
||||
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
||||
if (${pipe} === ${UTIL}.uninitialized) {
|
||||
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
||||
@ -187,16 +185,20 @@ export class ChangeDetectorJITGenerator {
|
||||
${pipe}.onDestroy();
|
||||
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
|
||||
}
|
||||
|
||||
${newValue} = ${pipe}.transform(${context}, [${argString}]);
|
||||
`;
|
||||
|
||||
var check = `
|
||||
if (${oldValue} !== ${newValue}) {
|
||||
${newValue} = ${UTIL}.unwrapValue(${newValue});
|
||||
${change} = true;
|
||||
${newValue} = ${UTIL}.unwrapValue(${newValue})
|
||||
${this._genChangeMarker(r)}
|
||||
${this._genUpdateDirectiveOrElement(r)}
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
`;
|
||||
|
||||
return r.shouldBeChecked() ? `${read}${check}` : read;
|
||||
}
|
||||
|
||||
_genReferenceCheck(r: ProtoRecord): string {
|
||||
@ -204,22 +206,32 @@ export class ChangeDetectorJITGenerator {
|
||||
var newValue = this._names.getLocalName(r.selfIndex);
|
||||
|
||||
var protoIndex = r.selfIndex - 1;
|
||||
var check = `
|
||||
|
||||
var read = `
|
||||
${this._names.getCurrentProtoName()} = ${this._names.getProtosName()}[${protoIndex}];
|
||||
${this._genUpdateCurrentValue(r)}
|
||||
`;
|
||||
|
||||
var check = `
|
||||
if (${newValue} !== ${oldValue}) {
|
||||
${this._names.getChangeName(r.selfIndex)} = true;
|
||||
${this._genChangeMarker(r)}
|
||||
${this._genUpdateDirectiveOrElement(r)}
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
`;
|
||||
|
||||
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
|
||||
|
||||
if (r.isPureFunction()) {
|
||||
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
|
||||
return `if (${condition}) { ${check} } else { ${newValue} = ${oldValue}; }`;
|
||||
if (r.isUsedByOtherRecord()) {
|
||||
return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
|
||||
} else {
|
||||
return `if (${condition}) { ${genCode} }`;
|
||||
}
|
||||
} else {
|
||||
return check;
|
||||
return genCode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,6 +279,10 @@ export class ChangeDetectorJITGenerator {
|
||||
rhs = `${UTIL}.${r.name}(${argString})`;
|
||||
break;
|
||||
|
||||
case RecordType.COLLECTION_LITERAL:
|
||||
rhs = `${UTIL}.${r.name}(${argString})`;
|
||||
break;
|
||||
|
||||
case RecordType.INTERPOLATE:
|
||||
rhs = this._genInterpolation(r);
|
||||
break;
|
||||
@ -293,6 +309,10 @@ export class ChangeDetectorJITGenerator {
|
||||
return res;
|
||||
}
|
||||
|
||||
_genChangeMarker(r: ProtoRecord): string {
|
||||
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
|
||||
}
|
||||
|
||||
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
|
||||
if (!r.lastInBinding) return "";
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {RecordType, ProtoRecord} from './proto_record';
|
||||
* Records that are last in bindings CANNOT be removed, and instead are
|
||||
* replaced with very cheap SELF records.
|
||||
*/
|
||||
export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
||||
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
|
||||
var res: List<ProtoRecord> = [];
|
||||
var indexMap: Map<number, number> = new Map<number, number>();
|
||||
|
||||
@ -24,8 +24,13 @@ export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
||||
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||
matchingRecord.referencedBySelf = true;
|
||||
|
||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||
if (record.argumentToPureFunction) {
|
||||
matchingRecord.argumentToPureFunction = true;
|
||||
}
|
||||
|
||||
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else {
|
||||
@ -40,7 +45,7 @@ export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
|
||||
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
||||
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
|
||||
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
||||
r.lastInBinding, r.lastInDirective);
|
||||
r.lastInBinding, r.lastInDirective, false, false);
|
||||
}
|
||||
|
||||
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
|
||||
@ -66,7 +71,8 @@ function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, a
|
||||
var contextIndex = _map(indexMap, r.contextIndex);
|
||||
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
|
||||
r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
|
||||
r.lastInBinding, r.lastInDirective);
|
||||
r.lastInBinding, r.lastInDirective, r.argumentToPureFunction,
|
||||
r.referencedBySelf);
|
||||
}
|
||||
|
||||
function _map(indexMap: Map<any, any>, value: number) {
|
||||
|
@ -83,9 +83,14 @@ export class CodegenNameUtil {
|
||||
if (i == CONTEXT_INDEX) {
|
||||
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
|
||||
} else {
|
||||
var changeName = this.getChangeName(i);
|
||||
declarations.push(`${this.getLocalName(i)},${changeName}`);
|
||||
assignments.push(changeName);
|
||||
var rec = this.records[i - 1];
|
||||
if (rec.argumentToPureFunction) {
|
||||
var changeName = this.getChangeName(i);
|
||||
declarations.push(`${this.getLocalName(i)},${changeName}`);
|
||||
assignments.push(changeName);
|
||||
} else {
|
||||
declarations.push(`${this.getLocalName(i)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
var assignmentsCode =
|
||||
@ -100,14 +105,18 @@ export class CodegenNameUtil {
|
||||
getAllFieldNames(): List<string> {
|
||||
var fieldList = [];
|
||||
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
|
||||
fieldList.push(this.getFieldName(k));
|
||||
if (k === 0 || this.records[k - 1].shouldBeChecked()) {
|
||||
fieldList.push(this.getFieldName(k));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
|
||||
var rec = this.records[i];
|
||||
if (rec.isPipeRecord()) {
|
||||
fieldList.push(this.getPipeName(rec.selfIndex));
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
||||
var dRec = this.directiveRecords[j];
|
||||
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
||||
|
@ -150,26 +150,30 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||
return null;
|
||||
}
|
||||
|
||||
var prevValue = this._readSelf(proto);
|
||||
var currValue = this._calculateCurrValue(proto);
|
||||
if (proto.shouldBeChecked()) {
|
||||
var prevValue = this._readSelf(proto);
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
|
||||
return change;
|
||||
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
return change;
|
||||
} else {
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
this._setChanged(proto, false);
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -215,6 +219,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||
|
||||
case RecordType.INTERPOLATE:
|
||||
case RecordType.PRIMITIVE_OP:
|
||||
case RecordType.COLLECTION_LITERAL:
|
||||
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
|
||||
|
||||
default:
|
||||
@ -227,28 +232,34 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||
var args = this._readArgs(proto);
|
||||
|
||||
var pipe = this._pipeFor(proto, context);
|
||||
var prevValue = this._readSelf(proto);
|
||||
var currValue = pipe.transform(context, args);
|
||||
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
||||
if (proto.shouldBeChecked()) {
|
||||
var prevValue = this._readSelf(proto);
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
currValue = ChangeDetectionUtil.unwrapValue(currValue);
|
||||
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||
if (proto.lastInBinding) {
|
||||
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
|
||||
return change;
|
||||
return change;
|
||||
|
||||
} else {
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._setChanged(proto, false);
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -284,7 +295,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||
|
||||
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
|
||||
|
||||
_setChanged(proto: ProtoRecord, value: boolean) { this.changes[proto.selfIndex] = value; }
|
||||
_setChanged(proto: ProtoRecord, value: boolean) {
|
||||
if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value;
|
||||
}
|
||||
|
||||
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
|
||||
return proto.isPureFunction() && !this._argsChanged(proto);
|
||||
|
@ -65,11 +65,23 @@ export class ProtoRecordBuilder {
|
||||
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
||||
oldLast.lastInDirective = false;
|
||||
}
|
||||
var numberOfRecordsBefore = this.records.length;
|
||||
this._appendRecords(b, variableNames);
|
||||
var newLast = ListWrapper.last(this.records);
|
||||
if (isPresent(newLast) && newLast !== oldLast) {
|
||||
newLast.lastInBinding = true;
|
||||
newLast.lastInDirective = true;
|
||||
this._setArgumentToPureFunction(numberOfRecordsBefore);
|
||||
}
|
||||
}
|
||||
|
||||
_setArgumentToPureFunction(startIndex: number): void {
|
||||
for (var i = startIndex; i < this.records.length; ++i) {
|
||||
var rec = this.records[i];
|
||||
if (rec.isPureFunction()) {
|
||||
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +89,7 @@ export class ProtoRecordBuilder {
|
||||
if (b.isDirectiveLifecycle()) {
|
||||
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
|
||||
[], -1, null, this.records.length + 1, b, null, false,
|
||||
false));
|
||||
false, false, false));
|
||||
} else {
|
||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
||||
}
|
||||
@ -145,12 +157,13 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
||||
|
||||
visitLiteralArray(ast: LiteralArray): number {
|
||||
var primitiveName = `arrayFn${ast.expressions.length}`;
|
||||
return this._addRecord(RecordType.PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
|
||||
this._visitAll(ast.expressions), null, 0);
|
||||
return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName,
|
||||
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
|
||||
0);
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap): number {
|
||||
return this._addRecord(RecordType.PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
|
||||
return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys),
|
||||
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
|
||||
0);
|
||||
}
|
||||
@ -208,11 +221,11 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
||||
if (context instanceof DirectiveIndex) {
|
||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
|
||||
selfIndex, this._bindingRecord, this._expressionAsString,
|
||||
false, false));
|
||||
false, false, false, false));
|
||||
} else {
|
||||
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
|
||||
selfIndex, this._bindingRecord, this._expressionAsString,
|
||||
false, false));
|
||||
false, false, false, false));
|
||||
}
|
||||
return selfIndex;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export enum RecordType {
|
||||
PIPE,
|
||||
INTERPOLATE,
|
||||
SAFE_PROPERTY,
|
||||
COLLECTION_LITERAL,
|
||||
SAFE_INVOKE_METHOD,
|
||||
DIRECTIVE_LIFECYCLE
|
||||
}
|
||||
@ -23,10 +24,17 @@ export class ProtoRecord {
|
||||
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
|
||||
public directiveIndex: DirectiveIndex, public selfIndex: number,
|
||||
public bindingRecord: BindingRecord, public expressionAsString: string,
|
||||
public lastInBinding: boolean, public lastInDirective: boolean) {}
|
||||
public lastInBinding: boolean, public lastInDirective: boolean,
|
||||
public argumentToPureFunction: boolean, public referencedBySelf: boolean) {}
|
||||
|
||||
isPureFunction(): boolean {
|
||||
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.PRIMITIVE_OP;
|
||||
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL;
|
||||
}
|
||||
|
||||
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
|
||||
|
||||
shouldBeChecked(): boolean {
|
||||
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
|
||||
}
|
||||
|
||||
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
|
||||
|
Reference in New Issue
Block a user