Optimize indexer access of an inline range expression#36147
Optimize indexer access of an inline range expression#36147agocke merged 7 commits intodotnet:masterfrom
Conversation
Introduces optimizations in lowering for an indexer access of the form receiver[a..b]. With the optimizations, the generation of intermediate System.Index and System.Range types will be avoided.
| } | ||
|
|
||
| BoundExpression endExpr; | ||
| switch (rangeExpr.RightOperandOpt) |
There was a problem hiding this comment.
consider changing to if (rangeExpr.RightOperandOpt is null) ... else ...
| @@ -384,44 +363,29 @@ .locals init (System.Span<int> V_0, //s | |||
| IL_0056: stloc.s V_4 | |||
| IL_0058: ldloca.s V_4 | |||
There was a problem hiding this comment.
ldloca.s V_4 [](start = 12, length = 14)
Is this temp here to avoid bad behavior from repeated reads of the receiver? Just trying to picture what scenarios would cause trouble if the original were used instead of a temp.
There was a problem hiding this comment.
Right, if the receiver is a property or method call (something with side effects) we don't want to evaluate it multiple times, so we create a temp.
|
@dotnet/roslyn-compiler for one more review please |
|
Looking #Closed |
| // int end = range.End.GetOffset(length) | ||
| // receiver.Slice(start, end - start) | ||
| // int rangeSize = range.End.GetOffset(length) - start | ||
| // receiver.Slice(start, rangeSize) |
There was a problem hiding this comment.
receiver.Slice(start, rangeSize) [](start = 14, length = 33)
Is there a design doc for ranges, where we can document the API patterns recognized by the compiler? #Closed
There was a problem hiding this comment.
| // during lowering, but it's simpler to always require them to prevent | ||
| // the user from getting surprising errors when optimizations fail | ||
| _ = GetWellKnownTypeMember(Compilation, WellKnownMember.System_Range__get_Start, diagnostics, syntax: syntax); | ||
| _ = GetWellKnownTypeMember(Compilation, WellKnownMember.System_Range__get_End, diagnostics, syntax: syntax); |
There was a problem hiding this comment.
GetWellKnownTypeMember [](start = 32, length = 22)
consider factoring the GetWellKnownTypeMember checks into a local function and calling here and above. #Closed
|
|
||
| bool usedLength = false; | ||
|
|
||
| if (rangeExpr.LeftOperandOpt is null) |
There was a problem hiding this comment.
rangeExpr.LeftOperandOpt is null [](start = 20, length = 32)
nit: Consider inverting if and use a pattern like below: if (rangeExpr.RightOperandOpt is BoundExpression left) .... Or invert the if below as Rikki already pointed out ;-) #Closed
|
|
||
| var F = _factory; | ||
|
|
||
| var localsBuilder = ArrayBuilder<LocalSymbol>.GetInstance(); |
There was a problem hiding this comment.
.GetInstance(); [](start = 57, length = 15)
On line 221, you use an optimistic size (3), but here and on line below you don't pass any size. Is that worth aligning? #Closed
There was a problem hiding this comment.
I'm making a very subtle guess here. For Index, there's a pattern optimization where we don't need the Length if the Index is from the start, being created inline via a conversion. So the code would have to look like:
int offset = ...;
int item = collection[(Index)offset];Note that this won't work if the offset is either from the end, or if there's no Index conversion (since the normal behavior is to call the indexer that takes an int directly).
So my prediction is that almost every pattern Index indexer optimization will be from the end. Thus, I hint that explicitly.
For Range, it's completely different. Code like 0..5 seems like it will very common, and because Range only takes Indexes, the construction requires the use of the Index -> int explicit conversion, that I try to remove optimistically. So in this case, if the user is making a range only from start, or only from end, they won't or will require the length, respectively.
Here, I have no idea which will be more common, so I don't hint anything.
Do you think this is worth a comment? #Closed
There was a problem hiding this comment.
|
|
||
| if (usedLength) | ||
| { | ||
| localsBuilder.Insert(1, lengthLocal.LocalSymbol); |
There was a problem hiding this comment.
Insert(1, [](start = 34, length = 10)
why not .Add(?
(also on next line) #Closed
There was a problem hiding this comment.
I believe side effects for the synthesized nodes are evaluated in order, so if we need the length, we need it before the bound nodes that use it, but after receiver, which is the first side effect calculated. #Closed
|
|
||
| BoundExpression startExpr; | ||
| BoundExpression rangeSizeExpr; | ||
| if (rangeArg is BoundRangeExpression rangeExpr) |
There was a problem hiding this comment.
if [](start = 12, length = 2)
The test cases I would expect for the new/optimized code: pattern indexer with .., ..y, x.. and x..y.
I wasn't able to identify test(s) for all of them. Can you confirm the coverage? #Closed
There was a problem hiding this comment.
I looked through and found a test for everything except ... I've added one in the latest commit. #Closed
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 5)
|
@jcouv Anything else? |
|
ping @jcouv |
Introduces optimizations in lowering for an indexer access of the form
receiver[a..b]. With the optimizations, the generation of intermediate
System.Index and System.Range types will be avoided.