perf(change_detection): use object pools not to create unnecessary garbage

This commit is contained in:
vsavkin 2015-01-27 17:30:32 -08:00
parent 62f08d38db
commit db0f0c462b
4 changed files with 105 additions and 29 deletions

View File

@ -43,7 +43,7 @@ import {
* var address0; * var address0;
* var city1; * var city1;
* var change; * var change;
* var changes = []; * var changes = null;
* var temp; * var temp;
* var context = this.context; * var context = this.context;
* *
@ -60,14 +60,15 @@ import {
* *
* city1 = address0.city; * city1 = address0.city;
* if (city1 !== this.city1) { * if (city1 !== this.city1) {
* changes.push(ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1)); * changes = ChangeDetectionUtil.addRecord(changes,
* ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
* this.city1 = city1; * this.city1 = city1;
* } * }
* *
* if (changes.length > 0) { * if (changes.length > 0) {
* if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]); * if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
* this.dispatcher.onRecordChange('address.city', changes); * this.dispatcher.onRecordChange('address.city', changes);
* changes = []; * changes = null;
* } * }
* } * }
* *
@ -144,7 +145,7 @@ ${localDefinitions}
${changeDefinitions} ${changeDefinitions}
var ${TEMP_LOCAL}; var ${TEMP_LOCAL};
var ${CHANGE_LOCAL}; var ${CHANGE_LOCAL};
var ${CHANGES_LOCAL} = []; var ${CHANGES_LOCAL} = null;
context = this.context; context = this.context;
${records} ${records}
@ -153,10 +154,10 @@ ${records}
function notifyTemplate(index:number):string{ function notifyTemplate(index:number):string{
return ` return `
if (${CHANGES_LOCAL}.length > 0) { if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]); if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL}); ${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL});
${CHANGES_LOCAL} = []; ${CHANGES_LOCAL} = null;
} }
`; `;
} }
@ -166,7 +167,8 @@ function structuralCheckTemplate(selfIndex:number, field:string, context:string,
return ` return `
${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context}); ${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context});
if (${CHANGE_LOCAL}) { if (${CHANGE_LOCAL}) {
${CHANGES_LOCAL}.push(${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL})); ${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
${field} = ${CHANGE_LOCAL}.currentValue; ${field} = ${CHANGE_LOCAL}.currentValue;
} }
${notify} ${notify}
@ -222,7 +224,8 @@ if (${cond}) {
} }
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) { function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`; return `${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
} }

View File

@ -19,6 +19,72 @@ export class SimpleChange {
} }
} }
var _simpleChangesIndex = 0;
var _simpleChanges = [
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null)
]
var _changeRecordsIndex = 0;
var _changeRecords = [
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null)
]
function _simpleChange(previousValue, currentValue) {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
function _changeRecord(bindingMemento, change) {
var index = _changeRecordsIndex++ % 20;
var s = _changeRecords[index];
s.bindingMemento = bindingMemento;
s.change = change;
return s;
}
var _singleElementList = [null];
export class ChangeDetectionUtil { export class ChangeDetectionUtil {
static unitialized() { static unitialized() {
return uninitialized; return uninitialized;
@ -128,11 +194,29 @@ export class ChangeDetectionUtil {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change); throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
} }
static simpleChange(previousValue:any, currentValue:any):SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static changeRecord(memento:any, change:any):ChangeRecord { static changeRecord(memento:any, change:any):ChangeRecord {
return new ChangeRecord(memento, change); return _changeRecord(memento, change);
} }
static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord { static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
return new ChangeRecord(memento, new SimpleChange(previousValue, currentValue)); return _changeRecord(memento, _simpleChange(previousValue, currentValue));
}
static addRecord(updatedRecords:List, changeRecord:ChangeRecord):List {
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
updatedRecords[0] = changeRecord;
} else if (updatedRecords === _singleElementList) {
updatedRecords = [_singleElementList[0], changeRecord];
} else {
ListWrapper.push(updatedRecords, changeRecord);
}
return updatedRecords;
} }
} }

View File

@ -62,7 +62,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (isPresent(change)) { if (isPresent(change)) {
currentGroup = proto.groupMemento; currentGroup = proto.groupMemento;
updatedRecords = this._addRecord(updatedRecords, proto, change); var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
} }
if (proto.lastInGroup && isPresent(updatedRecords)) { if (proto.lastInGroup && isPresent(updatedRecords)) {
@ -100,7 +101,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
this._setChanged(proto, true); this._setChanged(proto, true);
if (proto.lastInBinding) { if (proto.lastInBinding) {
return new SimpleChange(prevValue, currValue); return ChangeDetectionUtil.simpleChange(prevValue, currValue);
} else { } else {
return null; return null;
} }
@ -164,22 +165,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
return change; return change;
} }
_addRecord(updatedRecords:List, proto:ProtoRecord, change):List {
// we can use a pool of change records not to create extra garbage
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
updatedRecords[0] = record;
} else if (updatedRecords === _singleElementList) {
updatedRecords = [_singleElementList[0], record];
} else {
ListWrapper.push(updatedRecords, record);
}
return updatedRecords;
}
_readContext(proto:ProtoRecord) { _readContext(proto:ProtoRecord) {
return this.values[proto.contextIndex]; return this.values[proto.contextIndex];
} }

View File

@ -59,6 +59,10 @@ export function main() {
expect(dispatcher.log).toEqual(['name=misko']); expect(dispatcher.log).toEqual(['name=misko']);
dispatcher.clear(); dispatcher.clear();
cd.detectChanges();
expect(dispatcher.log).toEqual([]);
dispatcher.clear();
person.name = "Misko"; person.name = "Misko";
cd.detectChanges(); cd.detectChanges();
expect(dispatcher.log).toEqual(['name=Misko']); expect(dispatcher.log).toEqual(['name=Misko']);