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

explorer: use map for ExplorerItem children to reduce isEqualOrParent calls #46235

Merged
merged 6 commits into from Mar 22, 2018

Conversation

isidorn
Copy link
Contributor

@isidorn isidorn commented Mar 21, 2018

Fixes #45972

This is my attempt to remove all calls to isEqualOrParent from the explorer.
This PR takes advantage of the children array being already alhabetically sorted by names ( I am not 100% sure this is a fair assumption to take, but it is always sorted on my os x).
I decided to take this suggestion from @jrieken as it compared to others seemed to require the least amount of changes. (keeping children in a map would require to change all the method but we can do that as well).

I tried this out and seems to work, also all the explorer tests are passing.

Note that this is not thoroughly tested and just wanted to get some feedback if this approach makes sense thus I created this PR.
Also note that I did not measure performance which I could do in case we decide to take this route.

Let me know what you think

fyi @bpasero

@jrieken
Copy link
Member

jrieken commented Mar 21, 2018

This PR takes advantage of the children array being already alhabetically sorted by names ( I am not 100% sure this is a fair assumption to take, but it is always sorted on my os x).

You should be 100% sure about that!

I would still go with a map (O(1) is much better than O(log n)) - I understand the change is bigger but bigger is better, right ;-)

@isidorn
Copy link
Contributor Author

isidorn commented Mar 21, 2018

@jrieken the issue with the map is that I would probably have to keep both the map and the array in parallel, since there are a lot of outisde users of FileStat.children - you can do a Find All References on children.
But that also seems to be the isue with the sorted array, since we expose FileStat.children to everyone, I should go through every usage and try to keep this field read only so that people can not push items at the end as they want.

@jrieken
Copy link
Member

jrieken commented Mar 21, 2018

Yeah, tight coupling... I am sorry for you. The IFileStat is very much model like the internal explorer model and now you are in trouble.

@isidorn
Copy link
Contributor Author

isidorn commented Mar 21, 2018

After discussing with @jrieken and @bpasero we decided to

  • Rename FileStat to ExplorerItem and that it no longer inherits IFileStat
  • Change the children from an array to a map of child name to child
  • Keep the child name lowercase on mac and win and regular on Linux

I have tested out this changes and they look good. Unit tests are also passing and this is covered well I would say.

If we decide to merge it in I would create a test plan item so we get good coverage on every platform.

@jrieken @bpasero let me know if it is ok by you to merge or even better if you want to code review.

@isidorn isidorn changed the title explorer: use binary search for FileStat.find explorer: use map for ExplorerItem children to reduce isEqualOrParent calls Mar 21, 2018
return found;
});
return { exists, child: duplicateChild };
duplicateChild = parent.children[isLinux ? name : name.toLowerCase()];
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn this could be a utility method that takes care of the platform specific lowercasing (e.g. stat.hasChild)

public resource: URI;
public name: string;
public mtime: number;
public etag: string;
private _isDirectory: boolean;
private _isSymbolicLink: boolean;
public children: FileStat[];
public parent: FileStat;
public children: { [name: string]: ExplorerItem };
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn should we use a Map here instead?

oldLocalChildren.set(localChild.resource, localChild);
});
for (let name in local.children) {
oldLocalChildren.set(local.children[name].resource, local.children[name]);
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn suggest to extract a local variable to prevent duplicate access (var foo = local.children[name])

@@ -358,20 +368,20 @@ export class NewStatPlaceholder extends FileStat {
throw new Error('Can\'t perform operations in NewStatPlaceholder.');
}

public rename(renamedStat: NewStatPlaceholder): void {
public rename(renamedStat: IFileStat): void {
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn I think this can go back to NewStatPlaceholder

throw new Error('Can\'t perform operations in NewStatPlaceholder.');
}

public find(resource: URI): NewStatPlaceholder {
public find(resource: URI): ExplorerItem {
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn I think this can go back to NewStatPlaceholder

return null; //Unable to find
}

public findByPath(path: string, index: number): ExplorerItem {
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn private?

if (resources.isEqual(resource, this.resource, !isLinux /* ignorecase */)) {
return this;
if (resource && this.resource.scheme === resource.scheme && this.resource.authority === resource.authority &&
startsWith(resource.path, this.resource.path)
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn this checks seems to not work anymore if the path is the same but with different case? With our previous solution this would work. Also, given the code that is implemented in findByPath why do we actually need this extra check, isn't that covered by that other method already?

for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
if (this.children) {
while (index < path.length && path[index] === paths.sep) {
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn I understand the code below after some time but this could benefit from some comments what you are trying to achieve here

for (let childName in stat.children) {
this.getResolvedDirectories(stat.children[childName], resolvedDirectories);
}
stat.getChildrenNames().forEach(name => {
Copy link
Member

Choose a reason for hiding this comment

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

@isidorn use getChildrenArray here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bpasero yes I was debating about that. Not sure if object.keys + O(1) lookup is faster than conversion of whole map to array. Anyways getCHildrenArray is cleaner so can do it

@isidorn isidorn merged commit e9b719e into master Mar 22, 2018
@isidorn isidorn deleted the isidorn/explorerUseBinarySearchToFind branch March 22, 2018 10:13
@github-actions github-actions bot locked and limited conversation to collaborators Mar 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explorer: Reduce call count of isEqualOrParent
3 participants