Java中临时变量的效率

时间:2013-06-28 11:47:11

标签: java performance

我有一个班级

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;
        }
    }
}

哪一个是更好的编码方式?我在B班或C班中访问x of A的方式?

12 个答案:

答案 0 :(得分: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()的结果在两次调用之间发生变化(存在并发)。

答案 1 :(得分:2)

  

哪一个是更好的编码方式?

就可读性而言,这是有争议的,但没有什么区别。

就稳健性而言,C更好;见下文(最后),尽管你经常可以排除这些情况。

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

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

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

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

但是,我会预测:

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

(最后一次预测基于Davlik编译器之一的this Answer ......)


抢先微优化您的代码通常是一个坏主意:

  • 大多数时候,微观优化会浪费时间。除非执行此代码很多,否则任何性能差异都可能不明显。
  • 剩下的一些时间,微优化将无效......或实际上使事情变得更糟 1
  • 即使您的微优化适用于您的平台的一代,JIT编译器在更高版本中的更改可能会使微优化无效......或更糟。

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


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

答案 2 :(得分:1)

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

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

在你的例子中,它没有多少差异,但是在循环中它是。

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

答案 3 :(得分:1)

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

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

有几个原因:

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

答案 4 :(得分:0)

一个方法调用优于两个方法调用。

答案 5 :(得分:0)

在标题中,您要问哪个效率。我认为你的意思是表现明智。在这种情况下,对于一个简单暴露一个领域的典型吸气剂,如果两个案例结果不同,我会感到惊讶。

另一方面,

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

直到赋值为y它们是相同的 - temp var没有效果(因为在第一种情况下是内部生成的)。

但是,第一种情况将导致(通过Java规则)另一次调用getX以分配给y,而第二种情况将重用前一个值。

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

注意:但重要的是要了解两个版本在语义上相同。他们做不同的事情,可能会有不同的结果。

答案 8 :(得分:0)

如果x和y是您经常需要的坐标,请考虑直接访问: 如果你有一个吸气剂和一个二传手,那么你也可以将它们公之于众或受到保护。

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

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

(a.getX() + b.getX() + c.getX()) / 3.0;
如果正确的话,

并不容易证明:

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

答案 9 :(得分: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对象,那么我会担心别的事情,因为你不会有任何明显的区别。

答案 10 :(得分:0)

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

答案 11 :(得分:0)

本答案仅用于解决本评论中提出的一点:

  

编译器生成与第二种情况相同的第一种情况是非法的。如果方法调用出现在源中,则必须对其进行评估。毕竟,没有保证getX每次都返回相同的值,也没有保证getX不会修改a中的某个内部值。 - Hot Licks Jun 28 at 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. 在此基础上,消除第二次负荷。
  5. 事实上,第二次调用可以完全优化,明确推断该方法可能存在的副作用。