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
Optimize Evaluate #5676
Conversation
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
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.
Looks great! I've added a few comments inline.
src/Shared/FileUtilities.cs
Outdated
} | ||
int start = IsSlash(path[0]) ? 1 : 0; | ||
int end = IsSlash(path[path.Length - 1]) ? 1 : 0; | ||
return path.Substring(start, path.Length - start - end); |
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 would blow up on path.Substring(1, -1)
if the function was passed "/"
as the path argument.
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.
Fixed, thanks!
} | ||
|
||
/// <summary> | ||
/// Put all the global properties into our property bag | ||
/// </summary> | ||
private ICollection<P> AddGlobalProperties() | ||
private int AddGlobalProperties() |
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.
❤️ for all changes in this file. You are awesome.
src/Build/Evaluation/Evaluator.cs
Outdated
} | ||
|
||
return globalProperties; | ||
return count; |
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: Just return _data.GlobalPropertiesDictionary.Count
?
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 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)) |
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.
Are you sure this condition was not helping? FWIW, the caller is not performing this check.
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.
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.
src/Shared/EscapingUtilities.cs
Outdated
{ | ||
return String.Empty; | ||
} | ||
while (Char.IsWhiteSpace(escapedString[escapedStringLength])) |
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 doesn't look correct. The last character is at index escapedStringLength - 1
.
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.
Also, line 85:
(indexOfPercent <= (escapedStringLength - 3)) &&
instead of:
(indexOfPercent <= (escapedString.Length - 3)) &&
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.
Nice catch, thanks! No reason to check that digits are hex if they're white space.
src/Shared/EscapingUtilities.cs
Outdated
while (currentPosition < escapedString.Length && Char.IsWhiteSpace(escapedString[currentPosition])) | ||
{ | ||
currentPosition++; | ||
} | ||
if (currentPosition == escapedString.Length) | ||
{ | ||
return String.Empty; | ||
} | ||
while (Char.IsWhiteSpace(escapedString[escapedStringLength])) | ||
{ | ||
escapedStringLength--; | ||
} |
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.
side-note: #5663 includes a Trim() implementation operating on spans which could potentially be used here.
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.
One thing to note is that if Trim()
does not modify the span, and ToString()
is done on the span, it will allocate a new string:
https://github.com/Therzok/BenchPlayground/blob/820035919d2a47506841041fe7caea799dc939ea/BenchPlayground/Benchmarks/runtime/SpanToString.md
https://github.com/Therzok/BenchPlayground/blob/820035919d2a47506841041fe7caea799dc939ea/BenchPlayground/Benchmarks/runtime/SpanToString.cs
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.
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.
src/Shared/FileUtilities.cs
Outdated
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; |
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.
Wondering if there's a more elegant way to express this, the best I could come up with is:
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.
src/Build/Evaluation/Evaluator.cs
Outdated
@@ -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)); |
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: 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.
src/Shared/FileUtilities.cs
Outdated
} | ||
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; |
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 not reuse String.Trim(Char[]) to implement this?
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.
That's actually really nice. Thanks for the suggestion!
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.
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".
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'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.
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.
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?
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 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!
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.
And while I'm at it, I'm not sure why I should have to substring, then trim. Why not both at once?
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.
LGTM pending comments.
src/Build/Evaluation/Evaluator.cs
Outdated
|
||
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[] { '\\', '/' }); |
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: 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; |
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.
If this was in its own commit it'd be a lot easier to glance over, see that it's fine, and then ignore.
@@ -738,11 +738,6 @@ internal static string ExpandMetadataLeaveEscaped(string expression, IMetadataTa | |||
return expression; | |||
} | |||
|
|||
if (expression.Length == 0) |
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 can this be removed?
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. :)