-
Notifications
You must be signed in to change notification settings - Fork 587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pass1 and pass2 no longer cause a stack overflow for large graphs #228
Conversation
…e scope was being created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM. Left a few suggestions for comments.
lib/position/bk.js
Outdated
// These returned functions are the recursive and base function calls. By | ||
// returning the functions and calling them here, we do not add additional | ||
// function calls to the stack. If recursive case generates additional | ||
// functions calls, they will be prepended to the array. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicking the last sentence of this comment:
If the recursive case generates additional functions calls, they will be prepended to the array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/position/bk.js
Outdated
var min = _.reduce(blockG.outEdges(v), function(min, e) { | ||
pass2(e.w); | ||
return Math.min(min, xs[e.w] - blockG.edge(e)); | ||
return _.map(blockG.outEdges(v).reverse(), function(e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why reverse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was left over from a previous commit. I removed the reverse.
lib/position/bk.js
Outdated
return _.map(blockG.outEdges(v).reverse(), function(e) { | ||
return function() { return pass2(e.w); }; | ||
}).concat([function () { | ||
var min =_.reduce(blockG.outEdges(v), function(acc, e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a space between = and _
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/position/bk.js
Outdated
|
||
// This function handles iterating over the graph without adding functions | ||
// calls to the stack. The argument, helper, returns an array of functions. | ||
// These returned functions are the recursive and base function calls. By |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming "helper" to something more descriptive... maybe something like getNextStepTraversalFuncs . Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/position/bk.js
Outdated
var traversalFuncs = helper(v); | ||
|
||
while(traversalFuncs.length) { | ||
var funcsOrX = traversalFuncs.shift()(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
funcsOrX is a non-homogenous array, so I'd recommend adding a comment explicitly stating what types of things it can contain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/position/bk.js
Outdated
}, 0); | ||
return xs[v]; | ||
}]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried adding whitespace in various places but it didn't seem to make it clearer. Probably just leave it as-is.
lib/position/bk.js
Outdated
// function that will set xs[v] with the smallest coordinates. | ||
function pass1(v) { | ||
return _.map(blockG.inEdges(v).reverse(), function(e) { | ||
return function() { return pass1(e.v);}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add space after semicolon before close-curly-brace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/position/bk.js
Outdated
} | ||
|
||
// pass2 maps over the outEdges of a node and creates a recursive function | ||
// that will be called by iterate. At the end of those functions, there is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phrasing is awkward.
At the end of those functions, there is another function that will set Finally, it sets xs[v] with the greatest coordinates.
Also, thoughts on "to the greatest coordinates" versus "with the greatest coordinates"? Both here and above, in the comment for pass1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
I tried this change in Netron and seems to freeze for large graphs. Any ideas why this happens? Repro steps:
|
@lutzroeder |
For the example above time seems to have gone from ~2.5 seconds (old) to infinite (new). Haven't had time to dig in if that's a performance issue or a deadlock and what happens with different files but it seems to be a regression you should investigate. |
I can reproduce the issue, but I'm not sure how to debug Netron. My guess is that in the original, visited prevented an infinite of loop. In this PR though, visited is only checked in a parent scope from the recursion and it's not exiting the loop for nodes it's already seen. |
Netron is an Electron app so vscode with the Debugger for Chrome extension is one option (run |
I saw a lot of repeated 'v' in pass1 when I debugged. Once I moved the visited check to the pass functions, the graph was able to render quickly. Does this commit work you you @lutzroeder? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to an iterative approach is a nice improvement. However, if I'm not mistaken this could be done more compactly and efficiently by putting the nodes on a stack vs. concatenating lists of traversal functions. Also, I would expect treating the list as a stack vs a queue to be more efficient (Array pop vs shift). A stack would also make this DFS which is consistent with the previous approach. I don't recall if DFS vs BFS was important for these passes.
The way the traversal functions are being added is LIFO. The new functions are added to the front of the array and then we shift off the front of the array. We could build an ordered array of node ids, appending to it all children node ids. Then reverse the list and iterate over it to calculate the value for xs starting with the lowest children nodes. Is that what you meant? That changes the behavior from setting each xs value as soon as we can to setting all xs values at the end. |
This is roughly what I was imagining for pass 1. It keeps all of the code for the pass in one place, avoids creating closures, and avoids the more expensive // Pass 1
var xs = {};
var visited = {};
var stack = blockG.nodes();
var elem;
while ((elem = stack.pop())) {
if (xs.hasOwnProperty(elem)) {
continue;
}
if (visited[elem]) {
xs[elem] = blockG.inEdges(elem).reduce((max, e) => {
return Math.max(max, xs[e.v] + blockG.edge(e));
}, 0);
} else {
visited[elem] = true;
stack.push(elem);
stack = stack.concat(blockG.predecessors(elem));
}
} BTW, this is a bit more complicated than I originally imagined. I didn't look closely enough at the algorithm when I reviewed last time. |
I updated the PR with very nearly what you said. I separated pass1 and pass2 because having a reduce inside the while loop causes a warning. Reduce take a function and it references xs and blockG, which are outer scoped variables. Let me know if there is anything else I should update. "Functions declared within loops referencing an outer scoped variable may lead to confusing semantics." |
You're right, that function should not have been in a while loop. Thanks for the enhancement. I'll schedule this for release shortly. |
This PR causes pass1 and pass2 to return functions instead of making recursive calls. By returning functions, there is a fixed number of function calls on the stack, regardless of the size of the graph. This is a fix for issue 223.
I ran the tests, and they all pass. Thank you for having comprehensive tests!
@miriammelnick paired with me on the changes.