CLR中泛型的不同实例化的代码共享时间

本文关键字:代码 共享 时间 实例化 泛型 CLR | 更新日期: 2023-09-27 18:20:19

如果您有一个方法或类型Foo<T>,那么CLR可能会为不同的T编译多个版本。我知道所有引用类型共享同一版本。它是如何为structs工作的?对于不同的结构,代码有时是共享的,还是从不共享?例如,我可以想象,所有大小相同的结构体都共享代码。

我很感兴趣,因为我想知道以下例子:

interface IBar 
{
   void DoBar();
}
struct Baz : IBar 
{
   public void DoBar(){ ... }
}
struct Quux : IBar 
{
   public void DoBar(){ ... }
}

现在,如果我做以下操作:

public void ExecuteBar<T>(T bar) where T:IBar
{
   bar.DoBar();
}
ExecuteBar(new Baz());
ExecuteBar(new Quux());

这会生成两个版本的ExecuteBar吗?每个版本都直接(非虚拟)调用Bar.DoBar()Quux.DoBar()?或者调度是在运行时完成的?

CLR中泛型的不同实例化的代码共享时间

这个问题没有直接的答案,它在很大程度上取决于您使用的抖动以及泛型方法中存在的代码类型。

一个起点是抖动确实为每个单独的值类型生成了一个不同的方法。即使是在其他方面完全相同的结构。可以通过调试器看到的内容。使用Debug+Windows+反汇编查看生成的计算机代码。单步进入方法,使用Debug+Windows+Registers窗口,EIP/RIP寄存器会显示方法在内存中的位置。

但是,像这样的通用方法仍然有资格进行内联优化。对perf来说非常重要的是,它使整个方法消失,并且方法中的代码被注入到调用方方法中。在这种情况下,泛型方法之间的区别会消失。对于接口实现方法,通常不能指望会发生这种情况。但是,如果将方法体留空,则示例代码确实会发生这种情况。x86和x64抖动的结果不同。

你只能通过查看生成的机器代码来真正判断你得到了什么。请确保允许优化器执行其工作,即"工具+选项"、"调试"、"常规",取消选中"抑制JIT优化"复选框。当然,一定要确保你永远不要依赖这个问题的准确答案。此类实施细节如有更改,恕不另行通知。

不直接使用泛型定义。相反,构造的类型是在运行时创建的。

将为每个值类型参数创建一个构造。将为所有引用类型参数创建一个唯一的构造。

尽管如此,不同类型的asm指令并不相同。

我将您的代码放入LINQPad中,生成的IL显示ExcuteBar只有一个版本,它调用IBar.DoBar()

ExecuteBar:
IL_0000:  nop         
IL_0001:  ldarga.s    01 
IL_0003:  constrained. 01 00 00 1B 
IL_0009:  callvirt    UserQuery+IBar.DoBar
IL_000E:  nop         
IL_000F:  ret

方法ExecuteBar2(IBar条)看起来类似:

ExecuteBar2:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  callvirt    UserQuery+IBar.DoBar
IL_0007:  nop         
IL_0008:  ret 

编辑:

class C : IBar {
   public void DoBar(){}
}

引用类型作为参数传递到相同的方法中(Baz和Quux被装箱)