4

我在 Herb Stutter 上阅读了 Reddit 上的一篇文章:JIT 永远不会像原生那样快,有人发表评论说令人难以置信的是,有人称 Herb “误导”了 C# 使用虚拟方法而不是非虚拟方法(你可以在这里阅读这篇文章)。这让我开始思考,我做了一个快速的小程序,并注意到 C# 实际上确实为 CIL 生成了虚拟方法(callvirt vs call)。但后来有人告诉我,这并不容易,而且 JIT 可能会内联代码而不是使用 vtables 和动态调度。我启动了调试器并试图查看。这是我的简单程序:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            t.TestIt();
            t.TestOut();

        }
    }

    class Test
    {
        public Test() { }
        public void TestIt()
        {
            Console.WriteLine("TESTIT");
        }
        public void TestOut()
        {
            Console.WriteLine("TESTOUT");
        }
    }
}

然后这里是程序集:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        esi  
00000004  mov         ecx,439E68h 
00000009  call        FFCE0AD4 
0000000e  mov         esi,eax 
            t.TestIt();
00000010  call        704BBEB8 // Call to Console.WriteLine()
00000015  mov         ecx,eax 
00000017  mov         edx,dword ptr ds:[02A02088h] 
0000001d  mov         eax,dword ptr [ecx] 
0000001f  call        dword ptr [eax+000000D8h] 
            t.TestOut();
00000025  call        704BBEB8  // Call to Console.WriteLine()
0000002a  mov         ecx,eax 
0000002c  mov         edx,dword ptr ds:[02A0208Ch] 
00000032  mov         eax,dword ptr [ecx] 
00000034  call        dword ptr [eax+000000D8h] 
0000003a  pop         esi  

    }
0000003b  pop         ebp  
0000003c  ret     

我的问题是:通过查看程序集如何判断它是否使用动态调度?我的预感是因为这 4 条指令类似于我在编程语言课上所记得的:

0000002a  mov         ecx,eax 
0000002c  mov         edx,dword ptr ds:[02A0208Ch] 
00000032  mov         eax,dword ptr [ecx] 
00000034  call        dword ptr [eax+000000D8h]

我假设这是动态调度是否正确?如果是这样,是否还有其他明显的迹象?如果我错了,我怎么知道它是否是动态调度?

4

2 回答 2

3

间接调用例如call dword ptr [eax+000000D8h] 是使用虚拟表的标志

于 2012-04-03T15:04:16.490 回答
2

是的,这种模式查找类似 vtable 的东西,然后使用检索到的地址来执行函数调用

 00000032  mov         eax,dword ptr [ecx] 
 00000034  call        dword ptr [eax+000000D8h]

是动态调度(也称为动态绑定)的标志。该模式基本上执行以下操作:使用对象的地址推断对象类型(它实际上只是找到存储在对象内的 vtable 指针)并找到要调用的函数(知道它在 vtable 中的索引)。如果您已经知道对象的实际类型,另一种方法是直接调用正确的函数。

例如在 C++ 中:

class Class {
public:
    virtual void Method() {}
};

Class* object = new Object();
object->Method();
delete object;

在这里,编译器有足够的数据知道object存储类型对象的地址class Class,因此它可以发出直接(无 vtable 查找)调用,Class::Method()这当然更快。

于 2012-04-03T14:59:10.543 回答