Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Mitigate some of #495 via MRO member selection and analysis set optimization #517

Merged
merged 4 commits into from
Jan 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions src/Analysis/Engine/Impl/AnalysisHashSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,19 @@ public bool SetEquals(IAnalysisSet other) {
return true;
}

// Only access Count once to prevent wasted time in bucket locks.
var lCount = Count;
var rCount = other.Count;

if (lCount == 0 && rCount == 0) {
return true;
}

if (Comparer == other.Comparer) {
if (lCount != rCount) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This optimization may not work. Code below gets hash codes and collapses them into hash sets, which means that two buckets arrays with different amount of items will be considered equal. I can't say if that is correct or not.

Copy link
Member Author

Choose a reason for hiding this comment

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

If the comparers are the same, should there ever be a situation where two sets have different contents that are the same? To me that seems related to the issue of keys sometimes changing after being added (that Debug.Fail), except that this specific check is costly to do.

Copy link
Contributor

Choose a reason for hiding this comment

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

It maybe possible as a result of some AnalysisSet merges or if there is more than one default Bucket instance with 0 hash set.

Btw, have you tested if there is any significant benefit of this optimization?

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 I'm not sure how merging matters for this. Wouldn't Buckets.Count be updated when that occurs? Or does a union not actually do any unioning and instead concatenates?

I don't have any numbers, but it seemed like it shifted some CPU time out to other places when I was working on fastai. Namely, I started with the count checks, then saw a lot of CPU time used on accessing Count (since it's put behind a lock), then moved the access above.

Most of the CPU time in my testing comes from set comparison and tuples getting too large, which I wasn't able to cleanly limit. The MRO lookup change curbs things in some cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or does a union not actually do any unioning and instead concatenates?

I've seen sets with empty buckets under debugger, but I have no idea how to reproduce it and I don't know if that is a buggy result or an expected one. This code is quite muddy. @MikhailArkhipov , what is your opinion?

Namely, I started with the count checks, then saw a lot of CPU time used on accessing Count (since it's put behind a lock), then moved the access above.

Just for the reference. Thin locks are really fast, but I don't know if we have thin or fat lock here. GetHashCode is overridden, but it isn't the only thing that can promote thin lock to fat lock.

The MRO lookup change curbs things in some cases.

Yes, MRO fix is clear.

Copy link
Member Author

Choose a reason for hiding this comment

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

The locking I'm referring to is here: https://github.com/Microsoft/python-language-server/blob/cb1c3dfbad1c7a521857a5dae8eea7c94225a096/src/Analysis/Engine/Impl/AnalysisHashSet.cs#L95-L106

Now that I look more closely, most other functions grab Bucket at the start. The original function here did (by grabbing lBuckets and maybe grabbing rBuckets). Perhaps I should have moved those upward and operated on them instead of introducing two extra possible locks.

I'm happy to remove some of the more suspect length checks if you think this may be an issue (but some of them should still be valid).

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I see, the thin vs fat locking is past lock and more to do with object layout, so me showing where the locking is doesn't matter. Oops.

Copy link

@MikhailArkhipov MikhailArkhipov Jan 2, 2019

Choose a reason for hiding this comment

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

Different numbers of items (types) in buckets and yet equality seem odd to me. I am not overly concerned with keeping correct number of items in buckets since collecting types from various type of calls is generally not what we want to be doing. Ex, def f(a): return a called with int now has int return. Called then with str it is suddenly show as returning int, str. But that's not true, it is not returning combo or a union. And then user adds code calling with bool and it now it returns triplet... Which has nothing to do with the nature of the code.

Choose a reason for hiding this comment

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

New analysis has special types for call arguments and generics so the said function is considered returning type of its argument so no need to collect possible outcomes. f(1). will show completion for int members and f('a'). will yield str members without need to hold unions.

Choose a reason for hiding this comment

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

In general LGTM. I'd build insiders and let people try it out.

return false;
}

// Quick check for any unmatched hashcodes.
// This can conclusively prove the sets are not equal, but cannot
// prove equality.
Expand All @@ -290,17 +302,16 @@ public bool SetEquals(IAnalysisSet other) {
}
}

var otherHc = new HashSet<AnalysisValue>(other, _comparer);
foreach (var key in this) {
if (!otherHc.Remove(key)) {
return false;
}
}
if (otherHc.Any()) {
if (lCount == 0 || rCount == 0) {
return false;
}

return true;
if (lCount == 1 && rCount == 1) {
return _comparer.Equals(this.First(), other.First());
}

var hs = new HashSet<AnalysisValue>(other, _comparer);
return hs.SetEquals(this);
}

/// <summary>
Expand Down
4 changes: 3 additions & 1 deletion src/Analysis/Engine/Impl/AnalysisSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@ public static bool ContainsAny(this IAnalysisSet set, IAnalysisSet values) {
sealed class ObjectComparer : IEqualityComparer<AnalysisValue>, IEqualityComparer<IAnalysisSet> {
public static readonly ObjectComparer Instance = new ObjectComparer();

private ObjectComparer() { }

public bool Equals(AnalysisValue x, AnalysisValue y) {
#if FULL_VALIDATION
if (x != null && y != null) {
Expand Down Expand Up @@ -574,7 +576,7 @@ sealed class UnionComparer : IEqualityComparer<AnalysisValue>, IEqualityComparer

public readonly int Strength;

public UnionComparer(int strength = 0) {
private UnionComparer(int strength = 0) {
Strength = strength;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Analysis/Engine/Impl/Intellisense/AnalysisQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public async Task Handler(CancellationToken cancellationToken) {
}

private sealed class QueueItemComparer : IEqualityComparer<QueueItem> {
public static IEqualityComparer<QueueItem> Instance { get; } = new QueueItemComparer();
public static readonly IEqualityComparer<QueueItem> Instance = new QueueItemComparer();

private QueueItemComparer() { }
public bool Equals(QueueItem x, QueueItem y) => Equals(x.Key, y.Key);
Expand Down
6 changes: 5 additions & 1 deletion src/Analysis/Engine/Impl/LocationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ public bool Equals(ILocationInfo other) {
/// Provides an IEqualityComparer that compares line, column and project entries. By
/// default locations are equaitable based upon only line/project entry.
/// </summary>
public static IEqualityComparer<ILocationInfo> FullComparer { get; } = new FullLocationComparer();
public static IEqualityComparer<ILocationInfo> FullComparer => FullLocationComparer.Instance;

sealed class FullLocationComparer : IEqualityComparer<ILocationInfo>, IEqualityComparer<LocationInfo> {
public static readonly FullLocationComparer Instance = new FullLocationComparer();

private FullLocationComparer() { }

public bool Equals(LocationInfo x, LocationInfo y) => Equals(x, y);
public bool Equals(ILocationInfo x, ILocationInfo y) {
return x.StartLine == y.StartLine &&
Expand Down
4 changes: 2 additions & 2 deletions src/Analysis/Engine/Impl/OverloadResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ internal ParameterResult GetParameterResultFromParameterInfo(IParameterInfo para
}

class OverloadResultComparer : EqualityComparer<OverloadResult> {
public static IEqualityComparer<OverloadResult> Instance = new OverloadResultComparer(false);
public static IEqualityComparer<OverloadResult> WeakInstance = new OverloadResultComparer(true);
public static readonly IEqualityComparer<OverloadResult> Instance = new OverloadResultComparer(false);
public static readonly IEqualityComparer<OverloadResult> WeakInstance = new OverloadResultComparer(true);

private readonly bool _weak;

Expand Down
4 changes: 3 additions & 1 deletion src/Analysis/Engine/Impl/PythonAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,9 @@ private AggregateProjectEntry GetAggregateWorker(IProjectEntry[] all) {
}

class AggregateComparer : IEqualityComparer<IProjectEntry[]> {
public static AggregateComparer Instance = new AggregateComparer();
public static readonly AggregateComparer Instance = new AggregateComparer();

private AggregateComparer() { }

public bool Equals(IProjectEntry[] x, IProjectEntry[] y) {
if (x.Length != y.Length) {
Expand Down
4 changes: 4 additions & 0 deletions src/Analysis/Engine/Impl/Values/ClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,10 @@ public static IAnalysisSet GetMemberFromMroNoReferences(IEnumerable<IAnalysisSet
result = result.Union(ns.GetMember(node, unit, name));
}
}

if (result != null && result.Count > 0) {
break;
}
}
return result;
}
Expand Down