c ++与java中运行时多态性的成本比较

时间:2013-06-30 08:58:41

标签: java c++ polymorphism

我经常看到有人抱怨C ++中的虚函数成本。但同样的人不会对java中的运行时多态性有任何负面看法。

如果我必须在c ++中开发一个运行时运行的应用程序,它也是用c ++开发的,我会采用以下方法:

  1. 拥有带虚拟方法的基类,它们代表一个“抽象”应用程序。这是运行时的应用程序视图。应用程序开发人员继承基类并将其应用程序编译为可动态加载的共享库。
  2. 具有带虚拟方法的基类,它代表运行时的“服务”。运行时实现这些服务。应用程序通过这些接口类使用这些服务。运行时加载应用程序动态库,并使用其服务的实现对象初始化应用程序。
  3. 现在应用程序和运行时都有彼此的句柄,并使用虚方法调用相互调用。是的,涉及成本,但这也使我们将应用程序与运行时分离。如果没有虚拟方法,应用程序将始终与运行时具有链接时间依赖性。

    现在考虑Java中的类似案例。 Java还有需要实现的接口,并且必须有相似的运行时多态性成本。

    是我对java运行时多态性的理解吗?

    如果还涉及到Java的成本,为什么总是c ++得到愤怒的评论“哦有虚拟功能成本,并且使用虚拟功能来解耦部分应用程序的方法肯定不好< /强>”。这些人去哪儿,以防万一?他们什么都不说。

    我的问题是如何处理这些评论?有什么理性的论据可以支持C ++?

4 个答案:

答案 0 :(得分:2)

Java基本上是完全端到端的设计,以加快这种精确的情况:

  • 始终通过引用访问对象(这样您就可以在同一个类层次结构中混合和匹配不同的类型。您可以创建一个Fruit对象数组,它也可以自然地存储Banana对象。这不会影响性能但它使得使用运行时多态的代码更容易编写。
  • 它使用垃圾收集器,允许在分配后在内存中移动对象,这样,即使数组只存储引用,引用的对象也可以在连续的内存中打包在一起,以最大限度地减少缓存未命中,否则会影响性能严重
  • 语言是JIT(或在某些情况下解释),因此在运行时,JVM可以查看虚拟调用,并在许多情况下将其优化为常规函数调用。

C ++没有所有这些机制:必须通过C ++中的引用/指针来存储和访问对象既繁琐且容易出错,但效率也很低(数组成员指向的对象不会是彼此相邻分配,因此访问每个对象可能会导致缓存未命中)。 当C ++编译器遇到虚拟调用时,它通常无法确定将调用哪个函数,因此无法优化虚拟调用。当它无法做到这一点时,它也无法内联调用(C ++编译器在很大程度上依赖于性能)。

但另一方面,C ++也不会需要依赖它。相反,C ++为您提供了强大的静态多态性,可以经常使用,从而完全消除了开销。

所以是的,运行时多态性,虚拟调用和继承通常在C ++中更昂贵,因为它没有加速它所需的大量管道。 但与此同时,C ++也使得运行时多态性更难以使用,在许多情况下它提供了可以替代使用的替代方案。

人们经常声称“虚拟调用的成本只是一个指针间接”,但它有许多微妙的成本,如上所示:它禁止函数内联,并且它需要使用引用语义来处理对象,再次影响内存局部性,这会损害CPU缓存利用率。它具有广泛的效果,Java从头开始有效地设计,以尽可能多地补偿这些。 C ++不是,并且在大多数情况下,必须在使用运行时多态性时获得性能损失。

当然,典型的C ++程序员比Java程序员更关注性能(你不经常听到Java程序员讨论他们代码的CPU缓存利用率,例如

  

如果我必须在c ++中开发一个运行时运行它的应用程序,它也是用c ++开发的,我会采用以下方法&lt; ...&gt;

请不要。如上所述,C ++代码通常不会,也不应该使用运行时多态来解决每个问题。在Java中,它实际上是您可以访问的唯一工具,它已被使用,必须使用,并且应该被广泛使用。在C ++中,它是整个选项工具箱中的一个工具。当有替代品时,通常最好避免使用。

  

涉及成本,但这也使我们将app与运行时分离。如果没有虚拟方法,应用程序将始终与运行时具有链接时间依赖性。

和?有这样的链接时间依赖性是否有问题?你打算在应用程序运行时换掉它吗?

答案 1 :(得分:1)

我担心在这里写答案是徒劳的,因为这里必须有一个答案。

对于它没有过于“语言谨慎”,我认为使用Java的人不一定是QUITE,因此专注于C ++程序员的性能,这可能反映了“哦,我们将不得不担心开销“。

虚拟和非虚函数[1]之间肯定存在一些开销 - 有时虚拟版本无法在非虚拟函数中内联非虚函数。但是考虑到其他选择(使用switch或if语句来决定做什么)也很少有好处,并且有一些好的设计(不设计调用虚函数的东西来将两个整数加在一起,如果是的话将在紧密循环中调用 - 因为开销将是相当重要的 - 使用一个知道有数百或数千个整数加在一起的函数 - 如果可能的话当然)。

[1]开销包括间接读取this指针以找到vtable,然后在偏移量X处调用该函数。主要开销往往是使用“更多寄存器”,这会对代码的效率产生负面影响。

答案 2 :(得分:1)

使用虚拟方法时,C ++和Java之间的主要区别在于Java开发人员别无选择,因此Java开发人员在这种情况下不考虑性能影响。

此外,Java VM旨在处理虚拟调用。这有一些性能影响,但你无法衡量它,因为你没有别的办法。此外,较新的VM还可以在运行时内联虚拟方法。

在C ++中,你有一段历史。 C ++来自C,在早期没有动态链接。随着时间的推移,动态链接被添加,这个链接过程必须以兼容的方式完成。然后以与此兼容的方式添加虚拟功能。此外,第一个C ++编译器只是C的预处理器,因此必须将虚函数映射到现有的C结构。

因此,我认为C ++中虚函数的性能影响主要基于历史。

答案 3 :(得分:0)

可能有多种因素导致您所看到的意见分歧。

一个因素是,担心运行时多态性的性能损失的C ++人群可能与在Java中无偿使用运行时多态性的人群不同。

另一个因素是,当项目使用C ++时,通常意味着存在一些严格的性能要求。在这些情况下,程序员无论如何都会竭尽全力调整输入,算法和数据结构,以减少动态绑定,并帮助编译器和CPU预测和优化关键操作。在使用Java的环境中,通常要求比严格的性能要重要得多,在这些情况下,运行时多态性是不可避免的。

另一个非常重要的因素是C ++程序通常是AOT编译的,并且编译器默认情况下不能利用运行时出现的大量新信息。另一方面,JVM可以使用JIT编译器使关键区域的本机代码适应,以便为最可能的结果获得更高的性能。面向对象语言的框架在优化运行时绑定方面往往非常有效。