任务是最大化应用程序某个部分的性能。在阅读了一些Habr文章之后,在某处听到或阅读了一些东西后,我开始密封类和方法(并将一些小类变成结构)。已经提交后,我决定在一个小应用程序中检查这是否真的给出了结果。
class X
{
public int NonVirtual() => DateTime.Now.Millisecond;
public virtual int Virtual() => DateTime.Now.Minute;
}
class Y : X
{
public override int Virtual() => DateTime.Now.Millisecond;
}
class Program
{
private static volatile int TST = 0;
static int Slow(Y x) => x.Virtual();
static int Fast(Y y) => y.NonVirtual();
static void Main(string[] args)
{
int i = 0;
var stopwatch1 = new Stopwatch();
var stopwatch2 = new Stopwatch();
stopwatch1.Start();
var y = new Y();
for (i = 0; i < 100000000; i++)
{
TST = Fast(y);
}
stopwatch1.Stop();
Console.WriteLine("Time elapsed: {0}", stopwatch1.Elapsed);
stopwatch2.Start();
for (i = 0; i < 100000000; i++)
{
TST = Slow(y);
}
stopwatch2.Stop();
Console.WriteLine("Time elapsed: {0}", stopwatch2.Elapsed);
Console.ReadKey();
}
}
该字段TST被声明为volatile防止编译器优化调用。
起初我很惊讶根本没有区别,然后第一个周期快了几十毫秒,然后是第二个(尽管虚拟方法表和所有这些,假设虚拟方法应该是合乎逻辑的至少落后一点)。
然后我进入了 IL:
IL_0001: callvirt instance int32 PerformanceTests.X::Virtual()
这是一个虚方法调用。感觉真爽。第二次调用:
IL_0001: callvirt instance int32 PerformanceTests.X::NonVirtual()
对不起,什么?callvirt? 不应该在这里call吗?sealed也不会以任何方式影响虚方法的调用。
想问问比较有经验的同事,毕竟封课在性能方面有什么意义吗?另外,为什么在 IL 中调用非虚拟函数与调用虚拟函数相同?
更新:
@Grundy 在评论中写道callvirt- 因为该方法是在基类中声明的。重写代码,以便现在使用基类(即Y- 未使用)。callvirt所以它被调用。
正如评论中已经提到的,编译器用于
callvirt生成NullReferenceException.要获得纯
call语句,编译器必须确保类实例不能是null. 例子:IL-编码:如果代码稍作改动:
我们已经得到
callvirt了,因为编译器假定它GetTest可以返回null:sealed不会影响运行时的任何内容。它只是一个开发人员标记,告诉您在高级代码中可以做什么和不可以做什么。对于每个非
callvirt虚拟方法JIT,在每个方法之前插入一条附加指令call:单
cmp条指令的影响很小。