Skip to content
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

misc: drive-by code cleanup in BaseNode #7723

Merged
merged 4 commits into from
Mar 28, 2019
Merged

misc: drive-by code cleanup in BaseNode #7723

merged 4 commits into from
Mar 28, 2019

Conversation

brendankenny
Copy link
Member

@brendankenny brendankenny commented Mar 26, 2019

While starting work on robust TTI, there were some things that were a little hard to follow as I was stepping through graphs. Tried to make them a bit clearer in code and comments while keeping everything working the same.

@patrickhulce let me know if this feels like too much change for change's sake and I can scale it back :)

@brendankenny brendankenny changed the title misc: drive-by code cleanup in BaseNode misc: drive by code cleanup in BaseNode Mar 26, 2019
@brendankenny brendankenny changed the title misc: drive by code cleanup in BaseNode misc(base-node): drive-by code cleanup Mar 26, 2019
@brendankenny brendankenny changed the title misc(base-node): drive-by code cleanup misc: drive-by code cleanup in BaseNode Mar 26, 2019
@brendankenny
Copy link
Member Author

slightly better diff with ?w=1

if (predicate(node)) {
node.traverse(
node => idsToInclude.add(node.id),
node => node._dependencies.filter(parent => !idsToInclude.has(parent))
Copy link
Member Author

@brendankenny brendankenny Mar 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dangers of Set<any>...we were checking the parent node object against a set of string IDs, so weren't saving any time here :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice fix!!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this one is difficult to test since it's just a marginal time savings huh

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this one is difficult to test since it's just a marginal time savings huh

yeah, and even at its worst it's really not that bad compared to other things we do with the graph

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice looks pretty good to me! just the bit about skipping some cloning when there's not a predicate

// Walk back up dependencies, cloning all nodes in the path to this one.
node.traverse(
node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()),
node => node._dependencies.filter(parent => !idsToIncludedClones.has(parent.id))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get a comment on this one that explains the filter while we're at it? :) it even took me a sec to remember why this was happening at all

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get a comment on this one that explains the filter while we're at it?

good call

if (idsToIncludedClones.has(node.id)) return;
if (predicate && !predicate(node)) return;

// Walk back up dependencies, cloning all nodes in the path to this one.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we only do the reverse traversal when there's a predicate?

that's one nice feature of the previous impl I wouldn't want to lose

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we only do the reverse traversal when there's a predicate?

do you mean something like

// Walk down dependents.
rootNode.traverse(node => {
  if (idsToIncludedClones.has(node.id)) return;

  if (!predicate) {
    // Clone every node.
    idsToIncludedClones.set(node.id, node.cloneWithoutRelationships());
  } else if (predicate(node)) {
    // Walk back up dependencies, cloning all nodes in the path to this one.
    node.traverse(
      node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()),
      // Dependencies already cloned have already cloned ancestors, so no need to visit again.
      node => node._dependencies.filter(parent => !idsToIncludedClones.has(parent.id))
    );
  }
});

Or other ideas for formatting to make it less dense looking?

Another option is to make a default always true predicate, so it's a little clearer.

Agreed it is a bit weird to do the reverse traversal in the no predicate case. My thinking before was that we only have a single call of cloneWithRelationships without a predicate, so it didn't seem to be a super important case to call out (and since the filter line makes there be no next nodes for every node, it doesn't do any significant extra work)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's exactly what I meant :)

we could do an early return in the !predicate case to avoid the nesting? but IMO this is fine

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

getNext = node => node.getDependents();
}
const visited = new Set([this.id]);
const queue = [[/** @type {Node} */(/** @type {BaseNode} */(this))]];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this? I'm surprised we need to cast BaseNode for this hopefully this is something that was fixed :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this?

Annoyingly, yes. this extends BaseNode, but the lie is that it's a Node. Humans know the only types of BaseNode are CPUNode | NetworkNode so the cast to Node is ok, but the compiler accounts for the fact that we could be calling on a different class derived from BaseNode, so it's not necessarily compatible with Node.

So to cast while following the type assertion rules, we have to upcast to BaseNode and then downcast to Node. The other options is an assertion and a @ts-ignore...maybe that's more honest (albeit also ugly).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humans know the only types of BaseNode are CPUNode | NetworkNode so

I mean, even that's not entirely true. In tests we call traverse() with a graph of BaseNode nodes, but in real code it's much more convenient to only get CPUNode | NetworkNode back, so we should continue to pretend it's true :)

*/
_traversePaths(iterator, getNext) {
const stack = [[/** @type {Node} */(/** @type {BaseNode} */(this))]];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol I guess this is a queue :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol I guess this is a queue :)

your comment in hasCycle (which does DFS) underlined the humor:

// Get the last node in the stack (DFS uses stack, not queue)

I was thinking the approach changed at some point during the writing of traverse :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha yeah that was my guess as well when I was looking at this, I'll take your benefit of the doubt on this one lol 🤞

return nodesToVisit;
};
for (const nextNode of getNextNodes(node)) {
if (visited.has(nextNode.id)) continue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having trouble finding the substantial changes here, it's just inlining the getNext rewrite from before and eliminating traverse right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just inlining the getNext rewrite from before and eliminating _traversePaths right?

yeah, main thing was the getNext rewriting and call to _traversePaths was breaking my brain when I was first (re)reading it, and it seemed like traverse has proven its design, so it seemed ok to inline at this point.

This was probably the bigger one for me saying it's ok to push back on changes if you don't think it reads better :)

technically we save n-1 visited.add(node.id) calls, but that's not worth much and could have been done without all the other things.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, I like this. originally I had used _traversePaths in another method but that need hasn't materialized in the 1.5 years since so I think we're pretty safe :)

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@brendankenny brendankenny merged commit 21dda6a into master Mar 28, 2019
@brendankenny brendankenny deleted the iter branch March 28, 2019 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants