对接口进行编码而不是实现是否会影响性能?

时间:2009-05-06 07:47:01

标签: performance oop interface

在日常程序中,我甚至不会考虑针对接口而不是实现编码的可能性能损失。优势大大超过了成本。所以请不要提供关于良好OOP的通用建议。

然而在this帖子中,XNA(游戏)平台的设计者给出了他的主要论点,即没有设计他的框架的核心类来对抗界面,这意味着性能受到打击。看到它是在游戏开发的背景下,每个fps可能都很重要,我认为这是一个有效的问题要问自己。

有人有任何统计数据吗?我没有看到测试/测量这个的好方法,因为我不知道这个游戏(图形)对象应该记住什么含义。

8 个答案:

答案 0 :(得分:6)

编码到接口总是会更容易,只是因为接口(如果做得好)要简单得多。使用界面编写正确的程序显然更容易。

正如旧格言所说的那样,更快地制作正确的程序比使快速程序正确运行更容易。

所以编程接口,让一切正常工作,然后做一些分析,以帮助你满足你可能有的任何性能要求。

答案 1 :(得分:3)

首先我要说的是,共同的概念是程序员的时间通常更重要,而且当实现发生变化时,反对实现的工作可能会强制进行更多工作。

第二,使用正确的编译器/ Jit我会假设与使用实现本身相比,使用接口需要花费极少量的额外时间。 此外,模板等技术可以删除界面代码。

引用Knuth的话说:“我们应该忘记效率低,大约97%的时间说:过早的优化是所有邪恶的根源。”
所以我建议先编码好,只有当你确定界面有问题时才会考虑改变。

另外我会假设如果这个性能命中率是真的,大多数游戏都不会使用C ++的OOP方法,但事实并非如此,Article详细阐述了它。

很难以一般形式谈论测试,自然坏程序可能会花费大量时间在不良界面上,但我怀疑这对所有程序是否都是如此,所以你真的应该看看每个特定的程序。 / p>

答案 2 :(得分:3)

What Things Cost in Managed Code

“静态呼叫,实例呼叫,虚拟呼叫或接口呼叫的原始成本似乎没有显着差异。”

这取决于你的代码在编译时被内联了多少,这可以提高性能~5倍。

接口编码也需要更长的时间,因为你必须编写合同(接口),然后是具体的实现。

但以正确的方式做事总是需要更长时间。

答案 3 :(得分:2)

接口通常意味着对性能的一些命中(但是这可能会根据使用的语言/运行时而改变):

  1. 接口方法通常由编译器通过虚拟调用实现。正如另一位用户指出的那样,编译器无法对这些内容进行内联,因此您将失去这种潜在的收益。此外,他们至少添加了一些指令(跳转和内存访问),以便在代码段中获得正确的PC。
  2. 多种语言的接口也意味着图形,需要DAG(有向无环图)才能正确管理内存。在各种语言/运行时中,通过使用循环图,您实际上可以在托管环境中获得内存“泄漏”。这对系统中的垃圾收集器/内存施加了很大的压力(显然)。注意循环图!
  3. 某些语言使用COM样式接口作为其底层接口,只要将接口分配给本地接口,或者通过值传递给函数(用于生命周期管理),就会自动调用AddRef / Release。这些AddRef / Release调用可能会增加并且成本很高。有些语言已经解释了这一点,并且可能允许您将接口作为“const”传递,这将不会生成自动减少这些调用的AddRef / Release对。
  4. 以下是循环图的一个小例子,其中2个接口相互引用,并且两者都不会自动收集,因为它们的引用数始终大于1。

    interface Parent {
      Child c;
    }
    
    interface Child {
      Parent p;
    }
    
    function createGraph() {
      ...
      Parent p = ParentFactory::CreateParent();
      Child c = ChildFactory::CreateChild();
    
      p.c = c;
      c.p = p;      
      ...  // do stuff here
    
      // p has a reference to c and c has a reference to p.  
      // When the function goes out of scope and attempts to clean up the locals
      // it will note that p has a refcount of 1 and c has a refcount of 1 so neither 
      // can be cleaned up (of course, this is depending on the language/runtime and
      // if DAGS are allowed for interfaces).  If you were to set c.p = null or
      // p.c = null then the 2 interfaces will be released when the scope is cleaned up.
    }
    

答案 4 :(得分:1)

我认为对象生命周期和你正在创建的实例数量将提供粗粒度的答案。

如果你正在谈论会有数千个实例,生命周期很短的事情,我猜想用一个结构而不是一个类可能会更好,更不用说一个实现接口的类了。

对于更像组件,具有少量实例和中等到长寿命的东西,我无法想象它会产生很大的不同。

答案 5 :(得分:1)

IMO是的,但是出于基本设计原因,比虚拟调度或类似COM的接口查询或运行时类型信息所需的对象元数据或类似的东西更加微妙和复杂。所有这些都有相关的开销,但它在很大程度上取决于所使用的语言和编译器,还取决于优化器是否可以在编译时或链接时消除这种开销。但在我看来,有一个更广泛的概念性原因,为什么编码到接口意味着(不保证)性能影响:

  

编码到界面意味着你和你之间存在障碍   您想要访问和转换的具体数据/内存。

这是我看到的主要原因。作为一个非常简单的例子,假设你有一个抽象的图像界面。它完全抽象出其像素格式的具体细节。这里的问题是,最有效的图像操作通常需要那些具体的细节。我们无法使用高效的SIMD指令实现我们的自定义图像过滤器,例如,如果我们一次只有一个getPixel并且一次setPixel一个,而忽略了基础像素格式。< / p>

当然,抽象图像可以尝试提供所有这些操作,并且这些操作可以非常有效地实现,因为它们可以访问实现该接口的具体图像的私有内部细节,但只能保持这么长时间。因为图像界面提供了客户端想要对图像做的所有事情。

通常在某些时候,接口不能希望提供整个世界可以想象的所有功能,因此当面对性能关键问题同时需要满足各种需求时,这样的接口通常会泄漏其具体细节。例如,抽象图像可能仍然通过pixels()方法提供指向其基础像素的指针,该方法在很大程度上破坏了编码到接口的许多目的,但在大多数性能关键区域中经常成为必需。

总的来说,许多最有效的代码通常必须在某种程度上针对非常具体的细节编写,例如专门为单精度浮点编写的代码,专门为32位RGBA映像编写的代码,代码编写专门用于GPU,特别是用于AVX-512,特别是用于移动硬件等。所以有一个基本的障碍,至少我们到目前为止使用的工具,我们无法抽象出所有的东西,只是编码到接口而没有隐含的惩罚

当然,如果我们只能编写代码,那么我们的生活会变得如此简单,无论我们是处理32位SPFP还是64位DPFP,我们是否正在编写着色器等所有具体细节。有限的移动设备或高端桌面,并且所有这些都是最具竞争力的高效代码。但我们离这个阶段还很远。我们当前的工具仍然经常要求我们根据具体细节编写我们的性能关键代码。

最后,这是一个粒度问题。当然,如果我们必须逐个像素地处理事物,那么任何抽象出像素的具体细节的尝试都可能导致主要的性能损失。但是如果我们在图像层面上表达一些东西,比如“alpha将这两个图像混合在一起”,即使存在虚拟调度开销等,这也可能是一个非常微不足道的成本。因此,当我们致力于更高级别的代码时,通常对接口进行编码的任何隐含的性能损失都会降低到变得完全无关紧要的程度。但是总是需要低级代码,它可以逐像素地处理事物,每帧多次循环数百万次,并且编码到界面的代价可以带来漂亮的代码。如果只是因为它隐藏了编写最有效的实现所必需的具体细节,那就是实质性的惩罚。

答案 6 :(得分:0)

在我个人看来,当涉及到图形时,所有非常繁重的工作都被传递到GPU anwyay。这些可以释放CPU来执行程序流和逻辑等其他操作。我不确定在编程接口时是否存在性能损失,但考虑到游戏的性质,它们不是需要可扩展的东西。也许某些课程,但总的来说,我不认为游戏需要考虑到可扩展性。所以,继续编写实现代码。

答案 7 :(得分:0)

  

这意味着性能上升

设计师应该能证明他的意见。