Reduce memory in the traverse dependencies logic by reusing objects

Reviewed By: BYK

Differential Revision: D7110823

fbshipit-source-id: 575da43e99bc2ba82b6e7bdcf61e933e95535710
This commit is contained in:
Rafael Oleza 2018-03-01 06:00:25 -08:00 committed by Facebook Github Bot
parent 5bec7748e4
commit cd13bb80eb
2 changed files with 68 additions and 86 deletions

View File

@ -319,7 +319,7 @@ describe('edge cases', () => {
expect( expect(
getPaths( getPaths(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges), await traverseDependencies([...files], dependencyGraph, {}, edges),
), ),
).toEqual({ ).toEqual({
added: new Set(['/foo', '/baz-moved']), added: new Set(['/foo', '/baz-moved']),
@ -335,7 +335,7 @@ describe('edge cases', () => {
expect( expect(
getPaths( getPaths(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges), await traverseDependencies([...files], dependencyGraph, {}, edges),
), ),
).toEqual({ ).toEqual({
added: new Set(['/foo', '/qux']), added: new Set(['/foo', '/qux']),

View File

@ -41,7 +41,7 @@ type Result = {added: Map<string, DependencyEdge>, deleted: Set<string>};
* (a file should not be deleted if it has been added, but it should if it * (a file should not be deleted if it has been added, but it should if it
* just has been modified). * just has been modified).
**/ **/
type ResultWithModifiedFiles = { type Delta = {
added: Map<string, DependencyEdge>, added: Map<string, DependencyEdge>,
modified: Map<string, DependencyEdge>, modified: Map<string, DependencyEdge>,
deleted: Set<string>, deleted: Set<string>,
@ -68,7 +68,11 @@ async function traverseDependencies(
edges: DependencyEdges, edges: DependencyEdges,
onProgress?: (numProcessed: number, total: number) => mixed = () => {}, onProgress?: (numProcessed: number, total: number) => mixed = () => {},
): Promise<Result> { ): Promise<Result> {
const changes = []; const delta = {
added: new Map(),
modified: new Map(),
deleted: new Set(),
};
await Promise.all( await Promise.all(
paths.map(async path => { paths.map(async path => {
@ -78,14 +82,15 @@ async function traverseDependencies(
return; return;
} }
changes.push( delta.modified.set(edge.path, edge);
await traverseDependenciesForSingleFile(
edge, await traverseDependenciesForSingleFile(
dependencyGraph, edge,
transformOptions, dependencyGraph,
edges, transformOptions,
onProgress, edges,
), delta,
onProgress,
); );
}), }),
); );
@ -94,29 +99,26 @@ async function traverseDependencies(
const deleted = new Set(); const deleted = new Set();
const modified = new Map(); const modified = new Map();
for (const change of changes) { for (const [path, edge] of delta.added) {
for (const [path, edge] of change.added) { added.set(path, edge);
added.set(path, edge);
}
for (const [path, edge] of change.modified) {
modified.set(path, edge);
}
} }
for (const change of changes) { for (const [path, edge] of delta.modified) {
for (const path of change.deleted) { added.set(path, edge);
// If a dependency has been marked as added, it should never be included modified.set(path, edge);
// in as added. }
// At the same time, if a dependency has been marked both as added and
// deleted, it means that this is a renamed file (or that dependency
// has been removed from one path but added back in a different path).
// In this case the addition and deletion "get cancelled".
const markedAsAdded = added.delete(path);
if (!markedAsAdded || modified.has(path)) { for (const path of delta.deleted) {
deleted.add(path); // If a dependency has been marked as deleted, it should never be included
} // in the added group.
// At the same time, if a dependency has been marked both as added and
// deleted, it means that this is a renamed file (or that dependency
// has been removed from one path but added back in a different path).
// In this case the addition and deletion "get cancelled".
const markedAsAdded = added.delete(path);
if (!markedAsAdded || modified.has(path)) {
deleted.add(path);
} }
} }
@ -135,13 +137,25 @@ async function initialTraverseDependencies(
): Promise<Result> { ): Promise<Result> {
const edge = createEdge(dependencyGraph.getModuleForPath(path), edges); const edge = createEdge(dependencyGraph.getModuleForPath(path), edges);
return await traverseDependenciesForSingleFile( const delta = {
added: new Map([[edge.path, edge]]),
modified: new Map(),
deleted: new Set(),
};
await traverseDependenciesForSingleFile(
edge, edge,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
edges, edges,
delta,
onProgress, onProgress,
); );
return {
added: reorderDependencies(edge, delta.added),
deleted: delta.deleted,
};
} }
async function traverseDependenciesForSingleFile( async function traverseDependenciesForSingleFile(
@ -149,17 +163,19 @@ async function traverseDependenciesForSingleFile(
dependencyGraph: DependencyGraph, dependencyGraph: DependencyGraph,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
edges: DependencyEdges, edges: DependencyEdges,
delta: Delta,
onProgress?: (numProcessed: number, total: number) => mixed = () => {}, onProgress?: (numProcessed: number, total: number) => mixed = () => {},
): Promise<ResultWithModifiedFiles> { ): Promise<void> {
let numProcessed = 0; let numProcessed = 0;
let total = 1; let total = 1;
onProgress(numProcessed, total); onProgress(numProcessed, total);
const result = await processEdge( await processEdge(
edge, edge,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
edges, edges,
delta,
() => { () => {
total++; total++;
onProgress(numProcessed, total); onProgress(numProcessed, total);
@ -172,14 +188,6 @@ async function traverseDependenciesForSingleFile(
numProcessed++; numProcessed++;
onProgress(numProcessed, total); onProgress(numProcessed, total);
const modified = new Map([[edge.path, edge]]);
return {
added: reorderDependencies(edge, result.added),
deleted: result.deleted,
modified,
};
} }
async function processEdge( async function processEdge(
@ -187,9 +195,10 @@ async function processEdge(
dependencyGraph: DependencyGraph, dependencyGraph: DependencyGraph,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
edges: DependencyEdges, edges: DependencyEdges,
delta: Delta,
onDependencyAdd: () => mixed, onDependencyAdd: () => mixed,
onDependencyAdded: () => mixed, onDependencyAdded: () => mixed,
): Promise<Result> { ): Promise<void> {
const previousDependencies = new Set(edge.dependencies.values()); const previousDependencies = new Set(edge.dependencies.values());
const result = await dependencyGraph const result = await dependencyGraph
@ -217,15 +226,12 @@ async function processEdge(
edge.dependencies.set(relativePath, absolutePath); edge.dependencies.set(relativePath, absolutePath);
}); });
const deleted = [];
for (const absolutePath of previousDependencies.values()) { for (const absolutePath of previousDependencies.values()) {
if (!currentDependencies.has(absolutePath)) { if (!currentDependencies.has(absolutePath)) {
deleted.push(removeDependency(edge, absolutePath, edges)); removeDependency(edge, absolutePath, edges, delta);
} }
} }
const added = new Map([[edge.path, edge]]);
// Check all the module dependencies and start traversing the tree from each // Check all the module dependencies and start traversing the tree from each
// added and removed dependency, to get all the modules that have to be added // added and removed dependency, to get all the modules that have to be added
// and removed from the dependency graph. // and removed from the dependency graph.
@ -235,26 +241,18 @@ async function processEdge(
return; return;
} }
const dependencies = await addDependency( await addDependency(
edge, edge,
absolutePath, absolutePath,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
edges, edges,
delta,
onDependencyAdd, onDependencyAdd,
onDependencyAdded, onDependencyAdded,
); );
for (const [path, edge] of dependencies) {
added.set(path, edge);
}
}), }),
); );
return {
added,
deleted: flatten(deleted),
};
} }
async function addDependency( async function addDependency(
@ -263,47 +261,49 @@ async function addDependency(
dependencyGraph: DependencyGraph, dependencyGraph: DependencyGraph,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
edges: DependencyEdges, edges: DependencyEdges,
delta: Delta,
onDependencyAdd: () => mixed, onDependencyAdd: () => mixed,
onDependencyAdded: () => mixed, onDependencyAdded: () => mixed,
): Promise<Map<string, DependencyEdge>> { ): Promise<void> {
const existingEdge = edges.get(path); const existingEdge = edges.get(path);
// The new dependency was already in the graph, we don't need to do anything. // The new dependency was already in the graph, we don't need to do anything.
if (existingEdge) { if (existingEdge) {
existingEdge.inverseDependencies.add(parentEdge.path); existingEdge.inverseDependencies.add(parentEdge.path);
return new Map(); return;
} }
const edge = createEdge(dependencyGraph.getModuleForPath(path), edges); const edge = createEdge(dependencyGraph.getModuleForPath(path), edges);
edge.inverseDependencies.add(parentEdge.path); edge.inverseDependencies.add(parentEdge.path);
delta.added.set(edge.path, edge);
onDependencyAdd(); onDependencyAdd();
const {added} = await processEdge( await processEdge(
edge, edge,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
edges, edges,
delta,
onDependencyAdd, onDependencyAdd,
onDependencyAdded, onDependencyAdded,
); );
onDependencyAdded(); onDependencyAdded();
return added;
} }
function removeDependency( function removeDependency(
parentEdge: DependencyEdge, parentEdge: DependencyEdge,
absolutePath: string, absolutePath: string,
edges: DependencyEdges, edges: DependencyEdges,
): Set<string> { delta: Delta,
): void {
const edge = edges.get(absolutePath); const edge = edges.get(absolutePath);
if (!edge) { if (!edge) {
return new Set(); return;
} }
edge.inverseDependencies.delete(parentEdge.path); edge.inverseDependencies.delete(parentEdge.path);
@ -311,26 +311,20 @@ function removeDependency(
// This module is still used by another modules, so we cannot remove it from // This module is still used by another modules, so we cannot remove it from
// the bundle. // the bundle.
if (edge.inverseDependencies.size) { if (edge.inverseDependencies.size) {
return new Set(); return;
} }
const removedDependencies = new Set([edge.path]); delta.deleted.add(edge.path);
// Now we need to iterate through the module dependencies in order to // Now we need to iterate through the module dependencies in order to
// clean up everything (we cannot read the module because it may have // clean up everything (we cannot read the module because it may have
// been deleted). // been deleted).
for (const depAbsolutePath of edge.dependencies.values()) { for (const depAbsolutePath of edge.dependencies.values()) {
const removed = removeDependency(edge, depAbsolutePath, edges); removeDependency(edge, depAbsolutePath, edges, delta);
for (const removedDependency of removed.values()) {
removedDependencies.add(removedDependency);
}
} }
// This module is not used anywhere else!! we can clear it from the bundle // This module is not used anywhere else!! we can clear it from the bundle
destroyEdge(edge, edges); destroyEdge(edge, edges);
return removedDependencies;
} }
function createEdge(module: Module, edges: DependencyEdges): DependencyEdge { function createEdge(module: Module, edges: DependencyEdges): DependencyEdge {
@ -416,18 +410,6 @@ function reorderDependencies(
return orderedDependencies; return orderedDependencies;
} }
function flatten<T>(input: Iterable<Iterable<T>>): Set<T> {
const output = new Set();
for (const items of input) {
for (const item of items) {
output.add(item);
}
}
return output;
}
module.exports = { module.exports = {
initialTraverseDependencies, initialTraverseDependencies,
traverseDependencies, traverseDependencies,