-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
[WIP] Handle first class spans #35279
Conversation
@@ -45,7 +45,7 @@ public ByteArraySequenceEqualTranslator(ISqlExpressionFactory sqlExpressionFacto | |||
} | |||
|
|||
if (method.IsGenericMethod | |||
&& method.GetGenericMethodDefinition().Equals(MemoryExtensionsMethods.SequenceEqual) | |||
&& MemoryExtensionsMethods.SequenceEqual.Contains(method.GetGenericMethodDefinition()) |
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.
@ChrisJollyAU thanks for working on this, and sorry that I didn't have time to look earlier.
As per dotnet/runtime#109757 (comment) - which I hope you agree is a good approach to handle this - I think we'd be normalizing away method calls like MemoryExtensions.Contains - replacing them with corresponding non-Span calls - very early in the pipeline, in ExpressionTreeFuncletizer. If we go down this road, no later part of the query pipeline will ever see these MemoryExtensions methods.
Does that make sense?
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 did see that. It is one way to do it and was the alternative I considered. Not sure what the performance cost is though of rewriting the query away from span (implicit) back to normal versus handling it direct.
Note that I actually have it that there is only interpretation being done with very minimal changes to the funcletizer. Nothing is getting the full compilation. It just gets directly picked in in the translator mostly
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, take for example this
Span<string> data = new[] { "ALFKI" + "SomeVariable", "ANATR" + "SomeVariable", "ALFKI" + "X" };
var someVariable = "SomeVariable";
return AssertQuery(
async,
ss => ss.Set<Customer>().Where(c => data.Contains(c.CustomerID + someVariable)));
Currently this doesn't even compile (Expression tree cannot contain value of ref struct or restricted type) but should that support get added (that other thread has mentioned a couple of issues in order to get it working), rewriting it away would not be able to work. This handling it direct in the translator side would handle it (without much change if any I think)
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.
Not sure what the performance cost is though of rewriting the query away from span (implicit) back to normal versus handling it direct.
What I had in mind would be to do the rewriting as part of the funcletization pass, rather than adding an additional pass just for that; if we do it that way, the perf impact should be completely negligible, I think.
But more important: when the funcletizer identifies a tree fragment that can be client-evaluated, it does that and embeds the result either as a constant or as a parameter. If such an evaluatable tree fragment happens to contains a Contains, won't that now start throwing, since client evaluation involves going through the LINQ interpreter? Am I missing something here?
if so, then the funcletizer must do the substitution early (i.e. remove any Span-based method overloads), because it has to happen before the LINQ interpreter is possibly used.
Currently this doesn't even compile [...]
Right, but that seems pretty orthogonal to the discussion (and not necessarily extremely important). I indeed don't think the C# compiler will allow ref structs inside LINQ expression trees any time soon; that's also not a change from 9 to 10 - it has never been allowed, and as far as I know nobody has ever complained about it (there's no real reason to use a Span variable rather than an array when querying like this).
@roji Meant to ask, whats the thoughts from the rest of the team? |
@ChrisJollyAU on what specifically? |
on which sort of direction they would go for (rewrite back to enumerable or handle the MemoryExtensions functions direct in the translator)? |
Well, as I wrote above, unless my mental model is wrong, since the funcletizer does client-evaluation via the LINQ interpreter, and the latter doesn't support the |
Only needs to be rewritten IF you plan to simplify that expression via the interpreter. Otherwise you can leave everything in place with no modifications until you get to the translator. Probably best example is the Currently when it hits the expression in the Where
Interestingly unless I'm missing something, this is the only test that has changed like this. What I've got here on this PR, is literally a 1 line change (in |
I see now, that's the part I was missing. It's definitely an interesting (and viable) approach; but here are some thoughts:
Let me know what you think about the above. Regardless, I'll bring both these options up for discussion with the team. |
@ChrisJollyAU see #35339 for what my proposed approach looks like. |
Yeah just looking at it now (once I got 10.0 sdk installed).
I do see what you're wanting there. Ensure that the translators (and the specific provider related ones) only get one view of the expression that they need to handle |
Yeah, exactly. In general, where there's no semantic difference between to incoming constructs (e.g. It is worth keeping in mind, however, that some fragments may not get translated at all, but rather end up getting client-evaluated (part of an untranslatable top-level Select). Because of this, we must be careful with what we normailze, since sometimes two constructs that can't possibly be different when translated (e.g. to SQL) can definitely be different when executed locally; I was thinking about normalizing Equals to Finally, this case with the |
@ChrisJollyAU as #35339 has been merged, I'll go ahead and close this. Thanks for your work here and for the valuable conversation - it's still possible we'll do something along these lines in the future, and also if you see any further trouble with the approach in #35339 please let us know! I'll do my best to review your other PRs soon. |
@roji Thanks. This ended up an interesting problem and even though we didn't go for this approach, I think it was still good for exploring the problem and seeing some of the pros, cons and limitations. In the long term might only need this sort of thing if the add support for using a ref struct directly (like in the snippet from earlier). But from the other threads theres a lot of other work to be done before that could be supported. |
@ChrisJollyAU yeah, absolutely - and thanks for the valuable discussion around all this. I really doubt |
@roji Following from the discussion in #35100 this is what I have for handling first class spans
This is directly handling the span stuff.
A couple of comments:
op_Implicit
to convert to aSpan
.Compile
, doesn't handle the interpretation withSpan
typesMemoryExtensions.Contains
and other similar methods as well.Contains
is not a Queryable or Enumerable, this isn't handled in theQueryableMethodTranslatingExpressionVisitor
but in theRelationalSqlTranslatingExpressionVisitor
RelationalSqlTranslatingExpressionVisitor
. (Note: potential duplicate code for this and creating the OPENJSON)Couple of things still to do
Contains
and primitive collectionsPosting this as a draft so I can get some feedback if this is the direction to go in. Don't want to have to redo too much if we go a different route