Proposal: enforced inlining of functions, including lambdas passed into them #747
Replies: 41 comments
-
How could this possibly work for framework methods (like the ones in |
Beta Was this translation helpful? Give feedback.
-
@svick why doesn't it have access to their IL? |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox That's what reference assemblies are: assemblies containing only metadata, but no method bodies. It's also what .Net Standard is about: you compile against an assembly that contains no method bodies, that part is up to the framework that ends up running the code. |
Beta Was this translation helpful? Give feedback.
-
This can't work cross-assembly as you may be compiling against a reference assembly (as mentioned above), or the implementation in the other assembly can change by replacing the file on disk. I reckon it could work internally within a single assembly though. |
Beta Was this translation helpful? Give feedback.
-
But for things like the standard IEnumerable LINQ methods, you can ignore the actual implementation and inline the logic. |
Beta Was this translation helpful? Give feedback.
-
Can you, considering that .NET Core has a more optimised implementation than .NET Framework, or that the implementation can change for many reasons including perhaps when DIM rolls around? |
Beta Was this translation helpful? Give feedback.
-
This is what I'm talking about: (from x in new[] { 1, 2, 3 } where x % 2 == 1 select x * 10).Sum() Inlines the logic, not the extension method implementation, as: var total = 0;
foreach (var x in new[] { 1, 2, 3 })
{
if (x % 2 == 1) total += x * 10;
}
total The IL in the extension methods would be irrelevant because the compiler knows the semantics of System.Linq.Enumerable.Where, .Select, etc, and rolls its own ad-hoc specialized implementation in place. |
Beta Was this translation helpful? Give feedback.
-
Looks like reference assemblies throw a wrench in the works. I wonder if inlining is possible during JITting, not compilation. |
Beta Was this translation helpful? Give feedback.
-
Well, that would be a pretty drastic change in the language spec. Currently all of the LINQ keywords do explicitly translate to methods following the LINQ convention. It doesn't even care if they are instance methods or extension methods. This enables designing LINQ capable APIs that aren't enumerable, or even compile-time specialization of LINQ implementations. In fact, having the compiler explicitly rewrite LINQ keywords could break existing code. I can currently write extension methods to specialize LINQ implementations, e.g.: public static class FooExtensions {
public static IEnumerable<int> Where(this IEnumerable<int> source, Func<int, bool> predicate) { ... }
} And the compiler will prefer that implementation over the one provided by the BCL. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour It's not the LINQ keywords that would be transformed, it's the IOW this would also inline just the same: new[] { 1, 2, 3 }.Where(x => x % 2 == 1).Select(x =>x * 10).Sum() Your |
Beta Was this translation helpful? Give feedback.
-
The C# compiler knows nothing about where any type/method comes from. And I don't think it should. |
Beta Was this translation helpful? Give feedback.
-
And I do. =) At the very least, it should check the namespace and name like it does for ValueTuple. |
Beta Was this translation helpful? Give feedback.
-
Doesn't seem like the LDM does. They closed this very issue on Roslyn after declining it: In my opinion LINQ inlining is something ripe for a post-compiler IL transformer or AOT compilation, if not JIT. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
I think it's inconsistent to have semantic understanding of ValueTuple by namespace and name if we can't have semantic understanding of System.Linq.Enumerable methods by name. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour what good would a rewritten tree be when you don't have access to neither the source nor the CIL of another function during compilation? |
Beta Was this translation helpful? Give feedback.
-
This relates to "modifying generators". The problem is that there is no sane way to debug such programs. If you step into a rewritten tree, what do you expect to see? |
Beta Was this translation helpful? Give feedback.
-
@alrz What do I see when I step into an |
Beta Was this translation helpful? Give feedback.
-
Apparently nothing, because that ain't released yet mainly because of IDE story. Could be some With modifying generator we probably should see a whole new (rewritten) version of the compilation unit, even though only a part of it is being changed during compilation. |
Beta Was this translation helpful? Give feedback.
-
@alrz Sorry, I meant generator as in a method with |
Beta Was this translation helpful? Give feedback.
-
I don't know what it would take to support a new construct in the debugger or ENC but the compiler and IDE know exactly what would be generated for a async or iterator state machine, etc. Since the output is known, IDE can handle the debugger behavior but it surely does not light up automatically. With a modifying generator there are infinite possible outputs to be handled so it could be really challenging. |
Beta Was this translation helpful? Give feedback.
-
You mean for the content of the lambdas? IIRC Roslyn includes them inline with the syntax tree of the parent method, although I have very little experience working with Roslyn syntax nodes so I can't speak definitively as to how difficult this might be to interpret.
What would you expect to see if you stepped into a rewritten assembly? I guess in opting-in to such a step implies that the developer is throwing away the debug experience. It's a shame. I think that LINQ to Objects optimization would be a fantastic feature, but it seems none of the teams at Microsoft are particularly interested in seeing it implemented or owning it. With LINQ syntax being as abstract as it is it's a shame that it can't just produce the fastest implementation possible. Then maybe performance-sensitive codebases (like Roslyn) could actually make more use of it. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I mean for the content of Come to think of it, even when it's not LINQ to objects, but, say, @louthy's monads that you're trying to inline, the function you're trying to inline is in another assembly that you link dynamically to. You cannot do the inlining at compile time, since there's no guarantee that the library your executable will link to at runtime will be the same one. |
Beta Was this translation helpful? Give feedback.
-
Oh, yes. I was going more with @jnm2's approach of special-casing |
Beta Was this translation helpful? Give feedback.
-
You don't even have to special-case them, you could have |
Beta Was this translation helpful? Give feedback.
-
Expression trees can be overly restrictive. What if I want to use tuples? Or null-safe dereference? |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox That works well for queries that operate on large sequences, but not for queries that are executed many times. For those, you either allocate and probably compile the expression trees many times, or you manually implement some form of caching. And you still have one delegate invocation per query (that's much better than LINQ to objects, but not perfect). Rewriting C# or IL could offer better performance (no delegate invocation at all) and be more convenient than that (no need for manual caching, or anything like that). |
Beta Was this translation helpful? Give feedback.
-
@jnm2 That's #158. I think that proposal has a good chance of being implemented before this one. |
Beta Was this translation helpful? Give feedback.
-
@jnm2 @svick there is already a tool that rewrites queries. We only need some way to integrate is smoothly into build process. I was thinking about it for several months but without any results yet. |
Beta Was this translation helpful? Give feedback.
-
Was proposed since roslyn https://github.com/dotnet/roslyn/issues/11882 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Kotlin does that, and enforced inlining would greatly improve the performance of LINQ to objects and LINQ to monads.
Let's say I have the following code:
It currently generates the following CIL (boring no-op ctors skipped):
A super-intelligent compiler could've rewritten this as if I've written the following code:
I believe it should be possible to achieve something close to this code by inlining
S
and the lambda passed into it, even whenS
is from another assembly, as long asS
is static and doesn't read from or write to static fields:(Please ignore invalid labels and nonexistent stloc/ldloc.X opcodes)
The resulting method is longer than the super-optimized one, but should be faster than the original double invocation.
Syntax
I don't care if it's an attribute or a new contextual keyword. It should be an error to mark a function as inline if it cannot be inlined because it manipulates external state.
Open questions:
Beta Was this translation helpful? Give feedback.
All reactions