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

Optimize Evaluate #5676

Merged
merged 9 commits into from Sep 11, 2020
Merged

Optimize Evaluate #5676

merged 9 commits into from Sep 11, 2020

Conversation

Forgind
Copy link
Member

@Forgind Forgind commented Aug 25, 2020

In retrospect, this should have included the second commit from #5670, making that one just one commit. I can do that if you'd like, though I'd slightly prefer to keep it as-is just because that's less effort for me. :)

This is a hot path, and someone added an optimization to verify that expresions are nonempty before expanding them. This check was duplicated, however: every time it is called for these two methods, it already could not be empty. This removes the duplication.
Also inlined GetItemList and one of the private overloads of EvaluateCondition
Copy link
Member

@ladipro ladipro left a comment

Choose a reason for hiding this comment

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

Looks great! I've added a few comments inline.

}
int start = IsSlash(path[0]) ? 1 : 0;
int end = IsSlash(path[path.Length - 1]) ? 1 : 0;
return path.Substring(start, path.Length - start - end);
Copy link
Member

Choose a reason for hiding this comment

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

This would blow up on path.Substring(1, -1) if the function was passed "/" as the path argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed, thanks!

}

/// <summary>
/// Put all the global properties into our property bag
/// </summary>
private ICollection<P> AddGlobalProperties()
private int AddGlobalProperties()
Copy link
Member

Choose a reason for hiding this comment

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

❤️ for all changes in this file. You are awesome.

}

return globalProperties;
return count;
Copy link
Member

Choose a reason for hiding this comment

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

nit: Just return _data.GlobalPropertiesDictionary.Count?

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 hesitated briefly because it technically would change the behavior if there are already properties in that dictionary, which we could add, but then your way works and the current way doesn't, so it's a change we would make anyway. Thanks for the suggestion!

@@ -1938,7 +1933,7 @@ private static class ItemExpander
internal static string ExpandItemVectorsIntoString<T>(Expander<P, I> expander, string expression, IItemProvider<T> items, ExpanderOptions options, IElementLocation elementLocation)
where T : class, IItem
{
if (((options & ExpanderOptions.ExpandItems) == 0) || (expression.Length == 0))
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure this condition was not helping? FWIW, the caller is not performing this check.

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 caller checks the length before it expands metadata and properties. If it's just a property or just metadata, and it happens to be empty, there could be an extra string before this, but I think that's the only case. Still valid, so I added it back.

{
return String.Empty;
}
while (Char.IsWhiteSpace(escapedString[escapedStringLength]))
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look correct. The last character is at index escapedStringLength - 1.

Copy link
Member

Choose a reason for hiding this comment

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

Also, line 85:

(indexOfPercent <= (escapedStringLength - 3)) &&

instead of:

(indexOfPercent <= (escapedString.Length - 3)) &&

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice catch, thanks! No reason to check that digits are hex if they're white space.

Comment on lines 65 to 76
while (currentPosition < escapedString.Length && Char.IsWhiteSpace(escapedString[currentPosition]))
{
currentPosition++;
}
if (currentPosition == escapedString.Length)
{
return String.Empty;
}
while (Char.IsWhiteSpace(escapedString[escapedStringLength]))
{
escapedStringLength--;
}
Copy link
Member

Choose a reason for hiding this comment

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

side-note: #5663 includes a Trim() implementation operating on spans which could potentially be used here.

Copy link
Contributor

@Therzok Therzok Aug 27, 2020

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, the .NET Core implementation of ReadOnlySpan<char> does not recover the original string object in ToString() even if it spans the entire string. This is handled in #5663 by keeping track of the string reference separately so it can be returned if the span hasn't changed.

Comment on lines 235 to 237
int start = IsSlash(path[0]) ? 1 : 0;
int end = IsSlash(path[path.Length - 1]) ? 1 : 0;
return path.Length - start - end > 0 ? path.Substring(start, path.Length - start - end) : String.Empty;
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if there's a more elegant way to express this, the best I could come up with is:

Suggested change
int start = IsSlash(path[0]) ? 1 : 0;
int end = IsSlash(path[path.Length - 1]) ? 1 : 0;
return path.Length - start - end > 0 ? path.Substring(start, path.Length - start - end) : String.Empty;
int start = IsSlash(path[0]) ? 1 : 0;
if (path.Length == 1)
{
return path.Substring(start);
}
int end = IsSlash(path[path.Length - 1]) ? 1 : 0;
return path.Substring(start, path.Length - start - end);

Probably not any better, though.

@@ -652,7 +652,7 @@ private void Evaluate()
List<string> initialTargets = new List<string>(_initialTargetsList.Count);
foreach (var initialTarget in _initialTargetsList)
{
initialTargets.Add(EscapingUtilities.UnescapeAll(initialTarget.Trim()));
initialTargets.Add(EscapingUtilities.UnescapeAll(initialTarget, true));
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I prefer to include the parameter name for bool literal arguments to make it clear what the bool means without having to look at the signature of the called method.

}
int start = IsSlash(path[0]) ? 1 : 0;
int end = IsSlash(path[path.Length - 1]) ? 1 : 0;
return path.Length - start - end > 0 ? path.Substring(start, path.Length - start - end) : String.Empty;
Copy link
Contributor

@cdmihai cdmihai Aug 28, 2020

Choose a reason for hiding this comment

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

Why not reuse String.Trim(Char[]) to implement this?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's actually really nice. Thanks for the suggestion!

Copy link
Member

@ladipro ladipro Aug 28, 2020

Choose a reason for hiding this comment

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

String.Trim() removes all leading/trailing characters from the set, though. This behavior matches the name of the method but it's not what the method was doing. Is it guaranteed to not break anything?

Example: "\\hello"
Previously it would become "\hello", now after calling String.Trim() it is "hello".

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'm reasonably convinced that if there were multiple slashes on either side, that was a mistake because this is used for a path; there's no reason (unless you're explicitly trying to hack around a flawed implementation of EnsureNoLeadingSlash) to have multiple slashes in a row.

Additionally, to comment on the fact that I no longer escaped projectDirectoryNoRoot: it was already escaped just above there, so I believe that was extra work to no benefit. If there is a reason that double-escaping something (and presumably double-unescaping something) is preferred, I can revert that part.

Copy link
Member

Choose a reason for hiding this comment

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

Got it, thank you. How about the slash normalization done in FixFilePath() and called from EnsureNoLeadingSlash() and EnsureNoTrailingSlash()? Is it also something we know for sure wasn't needed?

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 am not confident about that and would actually guess that might be problematic, although the fact that it passed tests suggests it might be ok. To be safe, I'll add it back. Good catch!

Copy link
Member Author

Choose a reason for hiding this comment

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

And while I'm at it, I'm not sure why I should have to substring, then trim. Why not both at once?

Copy link
Contributor

@cdmihai cdmihai left a comment

Choose a reason for hiding this comment

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

LGTM pending comments.


int rootLength = Path.GetPathRoot(projectDirectory).Length;
string projectDirectoryNoRoot = projectDirectory.Substring(rootLength);
projectDirectoryNoRoot = FileUtilities.EnsureNoTrailingSlash(projectDirectoryNoRoot);
projectDirectoryNoRoot = EscapingUtilities.Escape(FileUtilities.EnsureNoLeadingSlash(projectDirectoryNoRoot));
projectDirectoryNoRoot = projectDirectoryNoRoot.Trim(new char[] { '\\', '/' });
Copy link
Member

Choose a reason for hiding this comment

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

nit: FileUtilities.Slashes instead of allocating a new array.

I misread Expander.cs's use of this, so they can't use the same method, but this retains the efficiency while only adding a little code.
@@ -308,7 +308,7 @@ public override string UnevaluatedValue

public override bool IsImported => Link.IsImported;

internal override string EvaluatedValueEscapedIntenral => Link.EvaluatedIncludeEscaped;
internal override string EvaluatedValueEscapedInternal => Link.EvaluatedIncludeEscaped;
Copy link
Member

Choose a reason for hiding this comment

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

If this was in its own commit it'd be a lot easier to glance over, see that it's fine, and then ignore.

src/Build/Evaluation/Evaluator.cs Show resolved Hide resolved
@@ -738,11 +738,6 @@ internal static string ExpandMetadataLeaveEscaped(string expression, IMetadataTa
return expression;
}

if (expression.Length == 0)
Copy link
Member

Choose a reason for hiding this comment

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

Why can this be removed?

@Forgind Forgind merged commit f577a68 into dotnet:master Sep 11, 2020
@Forgind Forgind deleted the refactor branch September 11, 2020 16: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.

None yet

5 participants