通过更多接口实现,方法调用成本是否保持不变

时间:2017-06-02 19:07:28

标签: java c# performance

让我们说我们有一个界面" ISomething"使用方法" DoSomething"。
我们创建了多个实现它的类。例5/10/100/1000新的类定义。

将迭代List< ISomething>无论添加到列表中的#unique类如何,调用DoSomething()都需要相同的时间。假设列表包含相同数量的对象。不同类型的比较列表与相同类型的列表。

基本上我问的是接口方法调用是否始终是"常量"运行时成本,或者它取决于它背后的#unique类型。

我为什么这么问?我需要了解界面如何在幕后工作,以免触发意外开销。

编辑:我可以稍微提问一下吗? 让我们考虑相反的情况,我们现在只有1个类,但它实现了N个接口(ISomething1 ... N)。无论在它实现的#interfaces上,性能是否相同。让我们说大多数接口最终指向相同的方法。

编辑2: adjan的答案提出了另一个潜在的问题:
他说c#为每个接口生成一个v表=>它的所有方法都花费了O(1)。

让我们考虑以下场景。
垂直继承链:A0延伸A1延伸A2 ...延伸AN 我们构建它们的所有这些类都没有变量。
我们刚刚创建了一个性能错误。

A0.method()花费O(N)。
I0.method()花费O(1)。

如果接口可以以恒定的开销完成工作,那么如果有人滥用继承机制并创建深度为N的垂直树分支,为什么要支付可变成本。

A0虚拟方法是否花费O(N)?它的构造函数或它的析构函数,我们100%安全地假设它需要不断的运行时成本。关于构造函数,对A0的构造函数的调用触发A1的无参数构造函数,因此调用链继续...因此O(N)成本。

3 个答案:

答案 0 :(得分:6)

首先关闭;这种过早的优化是疯狂的。通过接口调用所涉及的开销并不重要。

也就是说,混合类型不会产生运行时影响,除非您正在处理分支预测。 (见Why is it faster to process a sorted array than an unsorted array?)。

这样做的原因是,无论是否反复选择同一个虚拟函数指针表,都会产生调用虚拟函数指针表的开销。真;这不是你需要担心的事情。

答案 1 :(得分:3)

没有区别。

java和C#等语言实现虚拟方法调用的方式与C ++的方式非常相似。粗略地说,每个类都有一个虚方法表,它是一个指向方法的指针数组。此表中有一个条目用于由类或接口定义的每个虚拟方法。虚方法调用只需从该表中的特定索引中选择一个指针并调用它指向的函数。无论您是单个实现还是一百个实现,都没有区别。运行时成本与它一样恒定。

我无法想到其运行时复杂性与(Java或.Net)虚拟机中现有类实现的数量有任何关系的操作。

答案 2 :(得分:3)

调用接口方法时,使用所谓的v-table来确定被调用方法的内存地址。每种类型都有自己的v表,其中包含指向每个实现方法的指针,并且该类型的每个对象都有一个指向其类型的v-table的指针。因此,在方法调用期间,没有分支或搜索正确的内存地址,因为指针将始终唯一地指向正确的v表和方法地址。

另见此图:

enter image description here

编辑关于您的第二个问题。没有区别。对虚拟方法的调用总是相同的,因为您只访问相同数量的指针:

interface I1
{
   void DoIt();
}


interface I2
{
   void DoIt();
}


class A : I1, I2
{
   public void DoIt()
   {
      // do sth
   }
}

// Note: All v-tables point to the same implementation in A

I1 test1 = new A();
test1.DoIt(); // => lookup in I1's v-table

I2 test2 = (I2)test1;
test2.DoIt(); // => Lookup in I2's v-table

A a = (A)test2;
a.DoIt(); // => Lookup in A's v-table

图片来源:https://www.codeproject.com/KB/recipes/com-interception/vtable.png