私有函数是否比公共函数使用更多或更少的计算机资源



计算机资源是RAM、电源和磁盘空间。我只是好奇,尽管它或多或少有一点点。

理论上,在某些情况下,它可以更快地成为头发。在实践中,它们同样快。

使用invokevirtual字节码操作调用非静态、非公共方法。此操作码要求JVM动态查找实际的方法解析:如果您有一个静态编译为AbstractList::contains的调用,该解析为ArrayList::contains还是LinkedList::contains等?更重要的是,编译器不能只是在下次重用这次编译的结果;如果下一次调用myList.contains(val)时,它处于不同的实现中,该怎么办?因此,编译器必须至少对非私有方法进行一些量的检查,大致每次调用。

私有方法不能被重写,它们是使用invokespecial调用的。这个操作码用于各种方法调用,您可以只解析一次,然后永远不会更改:构造函数、对超级方法的调用等。例如,如果我在ArrayList::add中,并且我调用super.add(value)(这在那里没有发生,但让我们假设它发生了),那么编译器可以确定这是指AbstractList::add,因为类的超级类永远不会更改。

因此,用非常粗略的术语来说,invokevirtual调用需要解析该方法,然后再调用它,而invokespecial调用不需要解析方法(在第一次调用它之后——您必须至少解析一次所有内容!)。

JVM规范第5.4.3:节对此进行了介绍

invokedynamic指令一次出现的符号引用的解析并不意味着相同的符号引用被认为是针对任何其他invokedynamics指令解析的。

对于上面的所有其他指令,指令一次出现的符号引用的解析并不意味着相同的符号引用被认为是针对任何其他非调用动态指令解析的。

(原件中的empahsis)

好了,现在是"但你不会注意到区别"部分。JVM针对虚拟调用进行了大量优化。它可以做一些事情,比如检测某个站点总是特定地看到ArrayList,因此"静态"List::add调用实际上是ArrayList::add。要做到这一点,它需要验证传入对象是否真的是预期的ArrayList,但这非常便宜;如果以前的某个方法调用已经在该方法中完成了这项工作,则不需要再次发生。这被称为单态调用站点:尽管代码在技术上是多态的,但实际上列表只有一种形式

JVM优化单态调用站点,甚至优化双态调用站点(例如,列表总是ArrayListLinkedList,而不是其他任何内容)。一旦它看到三个表单,就必须使用一个完整的多态调度,这比较慢。但话说回来,在这一点上,你将苹果比作橙子:一个非私有的多态调用,指向一个定义为单态的私有调用。比较这两种单态调用(虚拟调用和私有调用)更公平,在这种情况下,如果可以检测到的话,你可能会发现差异很小。

我只是做了一个快速的JMH基准测试来比较(a)直接访问字段,(b)通过公共getter访问字段和(c)通过私有getter访问它。三个人花的时间都一样。当然,超级微基准测试很难做到正确,因为JIT可以通过优化来做如此美妙的事情。再说一遍,这就是的一点:JIT在优化方面做得非常好,以至于公共和私有方法都同样快。

私人函数比公共函数使用更多或更少的计算机资源吗?

没有。JVM使用相同的资源,而不考虑各个字段或方法上的访问修饰符。

但是除了资源利用率之外,还有更好的理由选择private(或protected);即封装。此外,我强烈建议您阅读开发者洞察系列:第1部分-编写愚蠢的代码。

我只是好奇,尽管它或多或少有一点点。

虽然好奇是件好事。。。如果你在编程时开始考虑这种事情,那么:

  • 你很容易浪费大量时间寻找不需要的微观优化,

  • 你的代码很容易无法维护,因为你牺牲了良好的设计原则和

  • 你甚至冒着让你的代码效率*低于不优化时的风险。


*-可以这样。1) 为了在测试平台上快速运行,您需要花费大量时间调整代码。2) 当您在生产平台上运行时,您会发现硬件为您提供了不同的性能特征。3) 升级Java安装,新JVM的JIT编译器会以不同的方式优化代码,或者它有一系列新的优化,这些优化被您的调整所禁止。4) 当您在真实世界的工作负载上运行代码时,您会发现作为调整基础的假设是无效的

最新更新