Skip to content

Commit feb929c

Browse files
authored
fix(performance): improve for loop performance (#14)
1 parent 905feee commit feb929c

File tree

3 files changed

+68
-19
lines changed

3 files changed

+68
-19
lines changed

src/comparators/object.comparator.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,21 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
3434
*/
3535
private currentValues: T[] = [];
3636

37+
/**
38+
* Stores the current nodes that have been used in a perfect match.
39+
*/
40+
private readonly usedCurrentValueNodeIds = new Set<number>();
41+
3742
/**
3843
* Stores the next nodes to compare.
3944
*/
4045
private nextValues: T[] = [];
4146

47+
/**
48+
* Stores the next nodes that have been used in a perfect match.
49+
*/
50+
private readonly usedNextValueNodeIds = new Set<number>();
51+
4252
/**
4353
* Threshold for next best match.
4454
*/
@@ -91,17 +101,19 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
91101
console.log('Starting next best match search for objects');
92102
currentValuesLength = this.currentValues.length;
93103
let bestMatches = [];
104+
const filteredCurrentValues = this.currentValues.filter(value => !this.usedCurrentValueNodeIds.has(value.nodeId));
105+
const filteredNextValues = this.nextValues.filter(value => !this.usedNextValueNodeIds.has(value.nodeId));
94106
if (this.threads <= 1) {
95-
for (const [i, value] of this.currentValues.entries()) {
96-
bestMatches.push(...this.findNextBestMatches(value));
107+
for (const [i, value] of filteredCurrentValues.entries()) {
108+
bestMatches.push(...this.findNextBestMatches(value, filteredNextValues));
97109

98110
if (i % 2500 === 0) {
99111
this.debug();
100112
console.log(`Progress: ${(i / currentValuesLength * 100).toFixed(2)}%`);
101113
}
102114
}
103115
} else {
104-
const bestMatchesHub = new NextBestFitHub(this.currentValues, this.nextValues, {threads: this.threads, threshold: this.threshold});
116+
const bestMatchesHub = new NextBestFitHub(filteredCurrentValues, filteredNextValues, {threads: this.threads, threshold: this.threshold});
105117
bestMatches = await bestMatchesHub.runComparison();
106118
}
107119

@@ -122,6 +134,10 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
122134
*/
123135
private findPerfectMatch(currentValue: NodeInput): boolean {
124136
const perfectMatch = this.nextValues.find(nextValue => {
137+
if (this.usedNextValueNodeIds.has(nextValue.nodeId)) {
138+
return false;
139+
}
140+
125141
try {
126142
return deepEqual(currentValue.node.obj, nextValue.node.obj);
127143
} catch {
@@ -139,8 +155,8 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
139155
aggregatorReference.currentNodeId.add(currentValue.nodeId);
140156
aggregatorReference.nextNodeId.add(perfectMatch.nodeId);
141157
this.results.perfectMatchNodes.set(valueHash, aggregatorReference);
142-
this.currentValues = this.currentValues.filter(_currentValue => _currentValue.nodeId !== currentValue.nodeId);
143-
this.nextValues = this.nextValues.filter(nextValue => nextValue.nodeId !== perfectMatch.nodeId);
158+
this.usedCurrentValueNodeIds.add(currentValue.nodeId);
159+
this.usedNextValueNodeIds.add(perfectMatch.nodeId);
144160
return true;
145161
}
146162

@@ -152,8 +168,12 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
152168
*
153169
* @param currentValue
154170
*/
155-
private findNextBestMatches(currentValue: NodeInput): FuzzyEqualSimilarity[] {
156-
return this.nextValues.map(nextValue => {
171+
private findNextBestMatches(currentValue: NodeInput, nextValues: NodeInput[]): FuzzyEqualSimilarity[] {
172+
return nextValues.map(nextValue => {
173+
if (this.usedNextValueNodeIds.has(nextValue.nodeId)) {
174+
return {similarity: 0, currentValueNodeId: 0, nextValueNodeId: 0};
175+
}
176+
157177
try {
158178
const totalSimilarity: FuzzyEqualComparison = fuzzyEqual(currentValue.node.obj, nextValue.node.obj);
159179
if (totalSimilarity.propertyCount === 0) {
@@ -198,10 +218,9 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
198218
aggregatorReference.nextNodeId.add(nextBestMatch.nextValueNodeId);
199219
usedCurrentNodes.add(nextBestMatch.currentValueNodeId);
200220
usedNextNodes.add(nextBestMatch.nextValueNodeId);
221+
this.usedCurrentValueNodeIds.add(nextBestMatch.currentValueNodeId);
222+
this.usedNextValueNodeIds.add(nextBestMatch.nextValueNodeId);
201223
similarityAggregatorReference.set(valueHash, aggregatorReference);
202-
203-
this.currentValues = this.currentValues.filter(_currentValue => _currentValue.nodeId !== nextBestMatch.currentValueNodeId);
204-
this.nextValues = this.nextValues.filter(nextValue => nextValue.nodeId !== nextBestMatch.nextValueNodeId);
205224
}
206225
}
207226

@@ -210,10 +229,18 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
210229
*/
211230
private fillDisjunctNodes(): void {
212231
for (const currentValue of this.currentValues) {
232+
if (this.usedCurrentValueNodeIds.has(currentValue.nodeId)) {
233+
continue;
234+
}
235+
213236
this.results.disjunctNodes.currentNodeId.add(currentValue.nodeId);
214237
}
215238

216239
for (const nextValue of this.nextValues) {
240+
if (this.usedNextValueNodeIds.has(nextValue.nodeId)) {
241+
continue;
242+
}
243+
217244
this.results.disjunctNodes.nextNodeId.add(nextValue.nodeId);
218245
}
219246

@@ -243,6 +270,6 @@ export class ObjectComparator<T extends NodeInput> implements BaseComparator<T,
243270
console.log('Perfect matches:', perfectMatchCounter);
244271
console.log('Next best nodes:', nextBestMatchCounter);
245272
console.log('Disjunct nodes:', {current: this.results.disjunctNodes.currentNodeId.size, next: this.results.disjunctNodes.nextNodeId.size});
246-
console.log('Available nodes:', {current: this.currentValues.length, next: this.nextValues.length});
273+
console.log('Available nodes:', {current: this.currentValues.length - this.usedCurrentValueNodeIds.size, next: this.nextValues.length - this.usedNextValueNodeIds.size});
247274
}
248275
}

src/comparators/primitive-type.comparator.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
1616
*/
1717
private currentValues: T[] = [];
1818

19+
/**
20+
* Stores the current nodes that have been used in a perfect match.
21+
*/
22+
private readonly usedCurrentValueNodeIds = new Set<number>();
23+
1924
/**
2025
* Stores the next nodes to compare.
2126
*/
2227
private nextValues: T[] = [];
2328

29+
/**
30+
* Stores the next nodes that have been used in a perfect match.
31+
*/
32+
private readonly usedNextValueNodeIds = new Set<number>();
33+
2434
/**
2535
* Stores the comparison results.
2636
*/
@@ -55,6 +65,7 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
5565

5666
console.log('Finished perfect match search for primitive types');
5767
this.fillDisjunctNodes();
68+
console.log('Finished matching for primitive types');
5869

5970
return this.results;
6071
}
@@ -66,7 +77,9 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
6677
* @returns indicator whether perfect match was found
6778
*/
6879
private findPerfectMatch(currentValue: T): boolean {
69-
const perfectMatch = this.nextValues.find(nextValue => currentValue.value === nextValue.value);
80+
const perfectMatch = this.nextValues.find(nextValue =>
81+
!this.usedNextValueNodeIds.has(nextValue.n) && currentValue.value === nextValue.value,
82+
);
7083

7184
if (perfectMatch) {
7285
const valueHash = currentValue.value?.toString() ?? 'undefined';
@@ -78,8 +91,8 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
7891
aggregatorReference.currentNodeId.add(currentValue.n);
7992
aggregatorReference.nextNodeId.add(perfectMatch.n);
8093
this.results.perfectMatchNodes.set(valueHash, aggregatorReference);
81-
this.currentValues = this.currentValues.filter(_currentValue => _currentValue.n !== currentValue.n);
82-
this.nextValues = this.nextValues.filter(nextValue => nextValue.n !== perfectMatch.n);
94+
this.usedCurrentValueNodeIds.add(currentValue.n);
95+
this.usedNextValueNodeIds.add(perfectMatch.n);
8396

8497
return true;
8598
}
@@ -92,10 +105,18 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
92105
*/
93106
private fillDisjunctNodes(): void {
94107
for (const currentValue of this.currentValues) {
108+
if (this.usedCurrentValueNodeIds.has(currentValue.n)) {
109+
continue;
110+
}
111+
95112
this.results.disjunctNodes.currentNodeId.add(currentValue.n);
96113
}
97114

98115
for (const nextValue of this.nextValues) {
116+
if (this.usedNextValueNodeIds.has(nextValue.n)) {
117+
continue;
118+
}
119+
99120
this.results.disjunctNodes.nextNodeId.add(nextValue.n);
100121
}
101122

@@ -116,6 +137,6 @@ export class PrimitiveTypeComparator<T extends PrimitiveRecord> implements BaseC
116137
console.log('----------');
117138
console.log('Perfect matches:', perfectMatchCounter);
118139
console.log('Disjunct nodes:', {current: this.results.disjunctNodes.currentNodeId.size, next: this.results.disjunctNodes.nextNodeId.size});
119-
console.log('Available nodes:', {current: this.currentValues.length, next: this.nextValues.length});
140+
console.log('Available nodes:', {current: this.currentValues.length - this.usedCurrentValueNodeIds.size, next: this.nextValues.length - this.usedNextValueNodeIds.size});
120141
}
121142
}

src/presenters/statistics.presenter.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class StatisticsPresenter<T extends BaseComparisonNodesInput> implements
6767
const heapSizeOfNextBestMatch = this.getHeapSizeOfNextBestMatch();
6868
const heapSizeOfDisjunctNodes = this.getHeapSizeOfDisjunctNodes();
6969
const totalNumberOfNodes = this.getTotalNumberOfNodes();
70-
const totalSizeDifference = this.calculateTotalHeapSizeDifference(heapSizeOfNextBestMatch, heapSizeOfDisjunctNodes);
70+
const totalSizeDifference = this.calculateTotalHeapSizeDifference(heapSizeOfPerfectMatch, heapSizeOfNextBestMatch, heapSizeOfDisjunctNodes);
7171

7272
const pathToWrite = path.join(this.options.filePath, this.options.fileName);
7373
const dataToWrite = JSON.stringify({
@@ -83,16 +83,17 @@ export class StatisticsPresenter<T extends BaseComparisonNodesInput> implements
8383
/**
8484
* Calculate the total heap size difference.
8585
*
86+
* @param heapSizeOfPerfectMatch
8687
* @param heapSizeOfNextBestMatch
8788
* @param heapSizeOfDisjunctNodes
8889
* @private
8990
*/
90-
private calculateTotalHeapSizeDifference(heapSizeOfNextBestMatch: HeapSizeOfNextBestMatch, heapSizeOfDisjunctNodes: HeapSizeOfDisjunctNodes): TotalHeapSizeDifference {
91+
private calculateTotalHeapSizeDifference(heapSizeOfPerfectMatch: HeapSizeOfPerfectMatch, heapSizeOfNextBestMatch: HeapSizeOfNextBestMatch, heapSizeOfDisjunctNodes: HeapSizeOfDisjunctNodes): TotalHeapSizeDifference {
9192
const {sizeByAccuracy} = heapSizeOfNextBestMatch;
9293
const {currentShallowHeapSize: currentDisjunctNodesShallowHeapSize, nextShallowHeapSize: nextDisjunctNodesShallowHeapSize} = heapSizeOfDisjunctNodes;
9394

94-
const currentShallowHeapSize = Object.values(sizeByAccuracy).reduce((accumulator, value) => accumulator + value.currentShallowHeapSize, 0) + currentDisjunctNodesShallowHeapSize;
95-
const nextShallowHeapSize = Object.values(sizeByAccuracy).reduce((accumulator, value) => accumulator + value.nextShallowHeapSize, 0) + nextDisjunctNodesShallowHeapSize;
95+
const currentShallowHeapSize = Object.values(sizeByAccuracy).reduce((accumulator, value) => accumulator + value.currentShallowHeapSize, 0) + currentDisjunctNodesShallowHeapSize + heapSizeOfPerfectMatch.currentShallowHeapSize;
96+
const nextShallowHeapSize = Object.values(sizeByAccuracy).reduce((accumulator, value) => accumulator + value.nextShallowHeapSize, 0) + nextDisjunctNodesShallowHeapSize + heapSizeOfPerfectMatch.nextShallowHeapSize;
9697
const difference = nextShallowHeapSize - currentShallowHeapSize;
9798
const percentage = (nextShallowHeapSize - currentShallowHeapSize) / nextShallowHeapSize * 100;
9899

0 commit comments

Comments
 (0)