3

I have a class

class A {
    private int x;
    public void setX(...){...}
    public int getX(){return x;}
}

class B {
    int y;
    public void setY() {
        //Accessing x of A, assume I already have object of A
        if(a.getX() < 0) {
             y = a.getX();
        }
    }
}

class C {
    int y;
    public void setY() {
        //Accessing x of A, assume I already have object of A
        int tmpX = a.getX();
        if(tmpX < 0) {
             y = tmpX;
        }
    }
}

Which one is better way of coding? The way I have accessed x of A in class B or in class C?

4

11 回答 11

12

让我们看看它编译成什么。我编译

class A {
    private int x;
    public void setX(int x_){x=x_;}
    public int getX(){return x;}
}

class B {
    int y;
    A a;
    public void setY() {
        //Accessing x of A, assume I already have object of A
        if(a.getX() < 0) {
             y = a.getX();
        }
    }
}

class C {
    int y;
    A a;
    public void setY() {
        //Accessing x of A, assume I already have object of A
        int tmpX = a.getX();
        if(tmpX < 0) {
             y = tmpX;
        }
    }
}

并获得 B

  public void setY();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field a:LA;
       4: invokevirtual #3                  // Method A.getX:()I
       7: ifge          21
      10: aload_0       
      11: aload_0       
      12: getfield      #2                  // Field a:LA;
      15: invokevirtual #3                  // Method A.getX:()I
      18: putfield      #4                  // Field y:I
      21: return        
}

对于 C

  public void setY();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field a:LA;
       4: invokevirtual #3                  // Method A.getX:()I
       7: istore_1      
       8: iload_1       
       9: ifge          17
      12: aload_0       
      13: iload_1       
      14: putfield      #4                  // Field y:I
      17: return        
}

因为C只调用getX一次它会更“高效”,因为这是那里最昂贵的东西。但是你真的不会注意到这一点。尤其是 HotSpot JVM 将非常快速地“内联”此方法调用。

除非这是正在运行的代码的主要部分,否则 优化它是没有意义的,因为您几乎不会注意到它。

然而,正如其他地方所提到的,除了性能之外,还有其他原因使该C方法更可取。一个明显的问题是getX()两个调用之间的变化结果(在存在并发的情况下)。

于 2013-06-28T11:53:54.400 回答
2

哪种编码方式更好?

在可读性方面,它是有争议的,但差别不大。

在健壮性方面,C更好;见下文(最后),尽管您通常可以排除这些情况。

就性能而言(这是您真正要问的),答案是它取决于平台。这取决于:

  • 无论您是在编译还是解释代码,
  • 如果您正在 JIT 编译该代码是否实际被编译,并且
  • 编译器/优化器的质量,以及有效优化的能力。

唯一确定的方法是创建一个有效的微基准,并使用您关注的特定平台实际测试性能。

(这也取决于是否getX()需要是虚拟调用;即是否是 X 的子类覆盖该getX()方法。)

但是,我会预测:

  • 在启用了 JIT 编译的 Java 热点系统上,JIT 将内联 getX() 调用(以虚拟调用问题为模),
  • 在早期的 Davlik VM 上,JIT 编译器不会内联调用,并且
  • 在最近的 Davlik VM 上,JIT 编译器将内联调用。

(最后的预测是基于Davlik 编译器中的一个人的这个答案......)


抢先对代码进行微优化通常是一个坏主意:

  • 大多数时候,微优化会浪费时间。除非此代码被大量执行,否则任何性能差异都可能不明显。
  • 其余的一些时间,微优化将无效......或者实际上使事情变得更糟1
  • 即使您的微优化在您的一代平台上工作,后续版本中的 JIT 编译器更改也可能导致微优化无效......或更糟。

1 - 我看到 Sun 编译器人员的建议,大意是“聪明的微优化”实际上可以阻止优化器检测到有用的优化是可能的。这可能不适用于此示例,但是...


最后,我要指出,在某些情况下,BC不是等效代码。想到的一种情况是,如果有人创建了AgetX方法具有隐藏副作用的子类;例如,调用getX会导致发布事件,或增加调用计数器。

于 2013-06-29T06:12:22.777 回答
1

您通常应该使用临时变量,即以下通常更好:

 int tmpX = a.getX();
 if(tmpX < 0) {
       y = tmpX;
 }

有几个原因:

  • 至少会一样快或更快。使用临时局部int变量非常便宜(很可能存储在 CPU 寄存器中)并且比额外的方法调用加上额外的字段查找的成本要好。如果幸运的话,JIT 可以将两者编译成等效的本机代码,但这取决于实现。
  • 并发性更安全-该字段x可能会在两个getX()调用之间被另一个线程更改。通常你只想读取一个值一次,并使用该值而不是处理两个可能不同的值和混淆结果的问题......
  • 如果将来有人让调用变得更复杂(例如添加日志记录,或计算 x 的值而不是使用字段),它肯定会更有效。getX()考虑长期可维护性。
  • 您可以通过分配给命名良好的临时变量来使用更好的名称。tmpX不是很有意义,但如果是这样的playerOneScore话,它会让你的代码更清晰。好的名称使您的代码更具可读性和可维护性。
  • 一般来说,最好的做法是尽量减少多余的方法调用。即使在这种特殊情况下无关紧要,最好养成这样做的习惯,以便在重要的情况下自动执行(例如,当方法调用导致昂贵的数据库查找时)。
于 2013-06-28T12:18:45.400 回答
1

C 更有效,因为 getter 被调用一次。

用户 Hot Licks 评论说编译器无法优化第二次调用,因为它不知道是否getX()会在第二次调用中传递另一个结果。

在您的示例中,它没有太大区别,但是在循环中却是。

用户 selig 证明了假设,他反编译并表明 C 更有效,因为 B 调用了两次方法。)

于 2013-06-28T11:51:13.500 回答
0

此答案仅用于解决此评论中提出的观点:

编译器生成与第二种情况相同的第一种情况是非法的。如果方法调用出现在源代码中,则必须对其进行评估。毕竟,不能保证 getX 每次都返回相同的值,也不能保证 getX 不会修改 a 中的某些内部值。– 热舔 6 月 28 日 11:55

这是有问题的代码:

    if(a.getX() < 0) {
         y = a.getX();
    }

getX()在哪里

    public int getX(){return x;}

(这种方法显然没有副作用。)

事实上,允许编译器优化第二次调用,假设它可以推断出当前线程中的任何内容都不会改变结果。允许忽略另一个线程所做的更改……除非存在使相关状态更改“发生在”观察状态的操作之前的操作。(换句话说,除非以线程安全的方式进行更改。)

在这种情况下,代码显然不是线程安全的。因此,允许编译器(或更准确地说,JIT 编译器)优化第二次调用。

但是,不允许字节码编译器进行这种优化。这两个类是独立的编译单元,字节码编译器必须允许(比如说)A在重新编译后可以修改和重新编译的可能性B。因此,字节码编译器不能确定在编译时A.getX()总是没有副作用B。(相比之下,JIT 可以进行这种推论......因为类在加载后无法更改。)

请注意,这只是编译器被允许做的事情。在实践中,它们可能更保守,尤其是因为这些优化往往执行起来相对昂贵。


我不知道 JIT 编译器的优化器是如何工作的,一个明显的方法是这样的;

  1. 推断这getX()是一个不需要虚拟方法分派的方法,因此是内联的候选者
  2. 将方法主体内联到两个点的调用中
  3. 执行本地数据流分析,显示同一变量在几条指令的空间内被加载两次
  4. 在此基础上,消除二次负荷。

所以事实上,第二个调用可以完全优化掉,明确推理该方法可能的副作用。

于 2013-07-05T10:10:15.530 回答
0

如果你想自己检查,你可以使用 aSystem.currentTimeMillis()然后运行代码几百万次(每次首先将任何创建的变量设置为其他变量以确保它被重置)然后System.currentTimeMillis()再次使用并减去以获得每个重复的总时间看看哪个更快。顺便说一句,除非您实际上要运行数百万次,否则我怀疑它会产生很大的不同。

于 2013-06-28T11:59:56.997 回答
0

在标题中,您要问哪个更有效。我认为你的意思是性能方面。在那种情况下,对于一个简单地公开一个字段的典型吸气剂,如果这两种情况结果有任何不同,我会感到惊讶。

另一方面,更好的编码方式往往是指可读性和结构化。在这种情况下,我个人会选择第二个。

于 2013-06-28T11:51:01.420 回答
0

B类中的方法将调用该方法两次,但C类中的方法将调用一次。所以C类方法更好

于 2013-06-28T11:51:18.917 回答
0

在分配给 y 之前,它们是相同的—— temp var 没有效果(因为在第一种情况下是在内部生成的)。

但是,第一种情况将导致(根据 Java 规则)再次调用 getX 以分配给 y,而第二种情况将重用先前的值。

(但 JITC 可能会将其展平并再次使它们相同。)

注意:重要的是要理解这两个版本在语义上并不相同。他们做不同的事情,可以有不同的结果。

于 2013-06-28T11:52:31.930 回答
0

如果 x 和 y 是您经常需要的坐标,请考虑直接访问:如果您有一个 getter 和一个 setter ,那么您也可以将它们设为 public 或 protected。

 if (a.x < 0) {
    y = a.x;
 }

这可能看起来有点反面向对象,但在现代语言中,你有一些属性可以避免公式中丑陋的吸气剂。该代码比您的副本更具可读性 getX()

(a.getX() + b.getX() + c.getX()) / 3.0;

如果beeing正确,则不容易证明:

(a.x + b.x + c.x) / 3.0;
于 2013-06-28T11:55:55.210 回答
0

如果你真的很关心你最好的选择是编写一个相当测试的代码并找出哪个执行得最快。问题是结果可能会根据您使用的 VM 版本而改变。

我最好的猜测是 c 类比 b 稍微好一点,因为它只需要一个方法调用。如果您最终确定临时 int,您甚至可能会获得更好的性能。我曾经测试过这个

for( int i = 0; i < foo.size(); i++ )

反对

for( int i = 0, n = foo.size(); i < n; i++ )

并发现后者更可取(这是与另一个程序员的争论,我赢了)。您遇到的情况可能非常相似,因为我猜您不会担心这一点,除非您创建数百万个 b 或 c 类对象。如果您没有创建数百万个 b / c 类对象,那么我会担心其他事情,因为您不会产生任何明显的差异。

于 2013-06-28T11:56:55.447 回答