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
Try skipping generation of empty method dictionaries #82591
Conversation
Say we're compiling `Foo<__Canon>.Method` for: ```csharp class Foo<T> { static void Method() => GenericMethod<List<T>>(); static void GenericMethod<T>() { } } ``` In the method body, we're generating a call to `GenericMethod<__Canon>` with a generic dictionary that we looked up from `Foo`s dictionary. But as you can see, the dictionary is empty because `GenericMethod` doesn't do anything with it's T. RyuJIT might even inline it. The problem is that we computed how the dictionary will look like during scanning and we're forever stuck with instruction to generate a generic dictionary for every `Foo<T>` instantiation. This is adding an optimization - if during scanning we find out that the dictionary layout of the target method is empty, we skip the slot. If RyuJIT ends up asking for something, we just give it garbage. The garbage will not be dereferenced because the dictionary layout was empty and there's nothing to look up from it. Saves 1.5% on BasicMinimalApi.
Can we tell RyuJIT to use It is confusing to pass garbage as the dictionary pointer. Also, managed debuggers give you better experience when the dictionary pointer is available, that won't be possible to do reliably if we start passing in garbage. |
What direction would you prefer? Make a null slot and tell RyuJIT to look up that (worse size, but easier to implement), or extend jitinterface to express this? |
It would be nice to tell the JIT to pass NULL as the dictionary pointer. |
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsSay we're compiling class Foo<T>
{
static void Method() => GenericMethod<List<T>>();
static void GenericMethod<T>() { }
} In the method body, we're generating a call to The problem is that we computed how the dictionary will look like during scanning and we're forever stuck with instruction to generate a generic dictionary for every This is adding an optimization - if during scanning we find out that the dictionary layout of the target method is empty, we skip the slot. If RyuJIT ends up asking for something, we just give it garbage. The garbage will not be dereferenced because the dictionary layout was empty and there's nothing to look up from it. Saves 1.5% on BasicMinimalApi. Cc @dotnet/ilc-contrib
|
Updated to do this. It shaves off another 2 kB, but I like it less. It was nicer when RyuJIT didn't need to be aware. |
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. Thanks!
Would it be worth it to extend this to callsites in non-generic code? |
I did a quick hack and it indicates about 0.08% savings for BasicMinimalApi so maybe not worth it given this would be another crosscutting thing that requires touching JitInterface. |
/azp run runtime-extra-platforms |
@EgorBo could you please have a look at the JIT change? |
Azure Pipelines successfully started running 1 pipeline(s). |
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.
Sorry for the delay, jit side LGTM!
Say we're compiling
Foo<__Canon>.Method
for:In the method body, we're generating a call to
GenericMethod<__Canon>
with a generic dictionary that we looked up fromFoo
s dictionary. But as you can see, the dictionary is empty becauseGenericMethod
doesn't do anything with it's T. RyuJIT might even inline it.The problem is that we computed how the dictionary will look like during scanning and we're forever stuck with instruction to generate a generic dictionary for every
Foo<T>
instantiation.This is adding an optimization - if during scanning we find out that the dictionary layout of the target method is empty,
we skip the slot. If RyuJIT ends up asking for something, we just give it garbage. The garbage will not be dereferenced because the dictionary layout was empty and there's nothing to look up from it.we instruct RyuJIT to generate a null generic context pointer.Saves 1.5% on BasicMinimalApi.
Cc @dotnet/ilc-contrib