我想出了一种替换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
(确保它是正确的)并使用objectInc
和代码大小直接跳转到虚函数的代码(而不是一堆比较)。
据我所知,这个方案的缺点是更多的内存(更大的类和更多的内联函数)和更多的编译器复杂性,但虚拟函数可以直接内联(整个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语句,可以通过使用有效的跳转列表进一步优化。
答案 0 :(得分:0)
我认为这样效率较低,因为它需要比较或跳转表等等,而通过vtable的间接方法调用很快:vtable中的偏移量可以在调用中进行硬编码,间接方法调用可用作为大多数处理器上的直接机器操作。
此外,每次将另一个后代添加到系统时,您的方法都需要重新编译。因此,对于像Java或.net这样的系统,即使在应用程序运行时也可以从Internet上加载代码,在运行时可能需要重新编译某些代码。说实话,这已经完成了撤消一些优化,但这只是你必须要做的另一种情况。
关于“对象必须存储它们的类型”:在.net和Java中,情况就是这样:每个对象都包含一个指向其类定义的指针,其中包含vtable。因此,每个类只有一个vtable,而不是每个对象。
答案 1 :(得分:0)
Smart Eiffel使用了一种非常类似的描述技术,其中大约80%的虚拟函数被内联。这种方法不允许vtable调度作为默认值来阻止动态链接,因此这对于通用目的来说可能是更合适的想法。