0

我想出了一种方法(不一定是新方法)来替换 switch 语句的虚拟表。这种方法允许以增加内存为代价内联虚函数。

代替使用表格查找,使用了一种开关

switch (objecttype)
{
   case objectA: inlined virtual function call for foo from objectA; break;
   case objectB: inlined virtual function call for foo from objectB; break;
   case objectC: inlined virtual function call for foo from objectC; break;
   default: vtable call;
}

因此,不是使用指针查找和调用,而是进行比较。可以对已知类进行代码内联。

为了使这个工作正常(避免不仅仅是函数调用),对象需要存储它们的类型。类型需要是顺序的。

例如:

class A
{
    ushort objectType; // internal id, say for class A it is 1000
    ushort objectInc; // internal. represents a sort of offset into the jump table
}

class B : A
{
    ushort objectInc; // one more than A's objectInc, has the same objectType
}

etc...

然后可以将switch语句做成一个高效的跳转表,比较objectType(确保正确)和usingobjectInc和代码大小直接跳转到虚函数的代码(而不是一堆比较)。

据我所知,这个方案的缺点是更多的内存(更大的类和更多的内联函数)和更多的编译器复杂性,但虚拟函数可以直接内联(整个 switch 语句可以),所以没有包装调用。由于一些比较和跳转(即O(1)),唯一的额外开销应该只是额外的几个周期。

有没有人对这种方案的性能以及为什么不使用它有任何有用的评论(我敢肯定我不是第一个想到这个的人)?我认为这将是相当有效的,除了由于比较可能导致缓存失效,但我认为平均而言,对于基类,它可以在直接内联方法调用的几个周期内完成。

顺便说一句,该表可以看作是每个对象派生的内联虚函数调用列表。

假设我们有以下内容:

class A
{
    void foo();
}

class B : A
{
    override void foo();
}

class C : A
{
    override void foo();
}


A a = new C();
a.foo();         // but calls fooWrap


/// internal
void fooWrap(A a)
{
    switch(a.Type)
    {
       case A: a.foo(); break; // A.foo() can be inlined here
       case B: b.foo(); break; // B.foo() can be inlined here
       case C: c.foo(); break; // C.foo() can be inlined here
       default: // use vtable lookup, a's type is not known at compile time
    }
}

(通常fooWrap是 vtable 查找)

现在fooWrap也可以直接内联,在这种情况下,调用的成本foo只是 switch 语句,可以通过使用有效的跳转列表进一步优化。

4

2 回答 2

0

Smart Eiffel 使用了一种非常相似的描述技术,大约 80% 的虚函数被内联。There 方法不允许将 vtable 调度作为默认值,这会阻止动态链接,因此这对于通用目的来说可能是一个更合适的想法。

http://smarteiffel.loria.fr/papers/oopsla97.pdf

于 2013-08-16T18:33:15.840 回答
0

我认为这效率较低,因为它需要比较或跳转表或其他任何东西,而通过 vtable 的间接方法调用速度很快:vtable 中的偏移量可以在调用中硬编码,间接方法调用可作为直接机器使用大多数处理器上的操作。

此外,每次将另一个后代添加到系统时,您的方法都需要重新编译。因此,对于像 Java 或 .net 这样允许在应用程序运行时甚至从 Internet 加载代码的系统,可能需要在运行时重新编译某些代码。老实说,这样做是为了撤消一些优化,但这只是你必须这样做的另一种情况。

关于“对象必须存储它们的类型”:在 .net 和 Java 中,情况已经如此:每个对象都包含一个指向其类定义的指针,其中包含 vtable 的其他信息。因此,每个类只有一个 vtable,而不是每个对象。

于 2013-08-16T16:22:34.750 回答