web-dev-qa-db-ja.com

Javaコンパイラは不要な三項演算子を最適化しますか?

一部のコーダーが「読みやすさのために」冗長な3項演算子を使用しているコードを確認しました。といった:

boolean val = (foo == bar && foo1 != bar) ? true : false;

当然のことながら、ステートメントの結果をboolean変数に割り当てるだけの方が適切ですが、コンパイラーは気にしますか?

25
Bakna

三元演算子を不必要に使用すると、元の意図とは逆に、コードがわかりにくくなり、読みにくくなるになる傾向があります。

そうは言っても、JVMでコンパイルされたバイトコードを比較することで、この点に関するコンパイラの動作を簡単にテストできます。
これを説明する2つのモッククラスを次に示します。

ケースI(3項演算子なし):

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c);
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}

ケースII(三項演算子を使用):

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c) ? true : false;
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}

ケースIのfoo()メソッドのバイトコード:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field Java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method Java/io/PrintStream.println:(Z)V
      23: return

ケースIIのfoo()メソッドのバイトコード:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field Java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method Java/io/PrintStream.println:(Z)V
      23: return

どちらの場合でも、バイトコードは同じです。つまり、コンパイラはvalブール値のコンパイル時に三項演算子を無視します。


編集:

この質問に関する会話は、いくつかの方向性の1つになっています。
上記のように、両方の場合(冗長な3値の有無にかかわらず)コンパイル済みJavaバイトコードは同一
Javaコンパイラによってこれが最適化)と見なされるかどうかは、最適化の定義に多少依存します。いくつかの点で、他の回答で複数回指摘されているように、そうではないと主張することは理にかなっています-どちらの場合も生成されたバイトコードは、三項に関係なく、このタスクを実行します。

ただし、主な質問について:

明らかに、ステートメントの結果をブール変数に代入する方が良いでしょうが、コンパイラは気にしますか?

単純な答えはノーです。コンパイラーは関係ありません。

27
yuvgin

Pavel HoralCodo および yuvgin の回答に反して、コンパイラーは最適化されないことを主張します(または無視)三項演算子。 (明確化:JavaをJITではなくバイトコードコンパイラに参照します)

テストケースをご覧ください。

クラス1:ブール式を評価し、変数に格納して、その変数を返します。

public static boolean testCompiler(final int a, final int b)
{
    final boolean c = ...;
    return c;
}

したがって、さまざまなブール式について、バイトコードを検査します。1.式:a == b

バイトコード

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
  1. 式:a == b ? true : false

バイトコード

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
  1. 式:a == b ? false : true

バイトコード

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: istore_2
  11: iload_2
  12: ireturn

ケース(1)と(2)は、コンパイラが3項演算子を最適化するためではなく、基本的にその3項演算子を毎回実行する必要があるため、まったく同じバイトコードにコンパイルされます。 trueまたはfalseを返すかどうかをバイトコードレベルで指定する必要があります。これを確認するには、ケース(3)を見てください。スワップされる行5と9を除いて、まったく同じバイトコードです。

次に何が起こり、逆コンパイルするとa == b ? true : falsea == bを生成しますか?最も簡単なパスを選択するのは、逆コンパイラの選択です。

さらに、「クラス1」の実験に基づいて、a == b ? true : falseはバイトコードに変換される点でa == bとまったく同じであると想定するのが妥当です。しかし、これは真実ではありません。次の「クラス2」を調べることをテストするために、「クラス1」との唯一の違いは、ブール結果を変数に格納せず、すぐにそれを返すことです。

クラス2:ブール式を評価し、結果を返します(変数に格納せずに)

public static boolean testCompiler(final int a, final int b)
{
    return ...;
}
    1. a == b

バイトコード:

   0: iload_0
   1: iload_1
   2: if_icmpne     7
   5: iconst_1
   6: ireturn
   7: iconst_0
   8: ireturn
    1. a == b ? true : false

バイトコード

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: ireturn
    1. a == b ? false : true

バイトコード

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: ireturn

ここで、a == bおよびa == b ? true : false式のコンパイル方法が異なることは明らかです、ケース(1)と(2)は異なるバイトコードを生成するため(ケース(2)と(3)は、予想どおり、5、9行だけがスワップされます)。

3つのケースすべてが同じであると予想していたので、最初はこれは驚くべきことでした(ケース(3)のスワップされた行5,9を除く)。コンパイラはa == bを検出すると、式を評価し、gotoを使用して行ireturnに移動するa == b ? true : falseの検出とは逆に、すぐに戻ります。これは、三項演算子の「true」の場合、if_icmpneチェックとgoto行の間に評価される可能性のあるステートメントのスペースを空けるために行われることを理解しています。この場合、それが単なるブールtrueであっても、コンパイラは、より複雑なブロックが存在する一般的な場合と同様に処理します
一方、「クラス1」の実験では、trueブランチと同様に、istoreiloadもあり、ireturngotoコマンドを強制し、ケース(1)と(2)でまったく同じバイトコードを生成します。

テスト環境に関するメモとして、これらのバイトコードは、IntelliJ IDEAが使用するjavacとは異なり、それぞれのECJコンパイラを使用する最新のEclipse(4.10)で作成されました。

ただし、javacで生成されたバイトコードを他の回答(IntelliJを使用)で読み取ると、少なくとも同じ値が格納され、すぐには返されない「クラス1」の実験にも同じロジックが適用されると思います。

最後に、他の回答( supercatjcsahnwaldt による回答など)ですでに指摘したように、このスレッドとSOの他の質問の両方で、重い最適化は次のように行われます。 Java-> Java-bytecodeコンパイラではなく、JITコンパイラ。したがって、これらの検査は、バイトコード変換に役立つ一方で、最終的に最適化されたコードがどのように実行されるかを適切に測定するものではありません。

補足: jcsahnwaldt の答えは、javacとECJが生成したバイトコードを同様のケースで比較します

(免責事項として、私はJavaコンパイルまたは逆アセンブリを実際に実行するためにそれほど詳しく調べていません。私の結論は主に上記の実験の結果に基づいています。)

9
tryman

はい、Javaコンパイラは最適化します。簡単に確認できます:

public class Main1 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz ? true : false;
  }
}

javac Main1.Javaおよびjavap -c Main1

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

public class Main2 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz;
  }
}

javac Main2.Javaおよびjavap -c Main2

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

どちらの例も、まったく同じバイトコードになります。

6
Pavel Horal

Javacコンパイラは通常、バイトコードを出力する前にコードを最適化しようとしません。代わりに、Java仮想マシン(JVM)とジャストインタイム(JIT)コンパイラを使用して、バイトコードをマシンコードに変換し、構造がより単純なものと同等になるようにします。 。

これにより、Javaコンパイラの実装が正しく機能しているかどうかの判断がはるかに簡単になります。ほとんどの構成要素は、定義済みの1つのバイトコードシーケンスでしか表現できないためです。コンパイラが他のバイトコードシーケンスを生成する場合、それは壊れていますそのシーケンスが元のシーケンスと同じように動作する場合でも

Javacコンパイラのバイトコード出力を調べることは、構造が効率的に実行されるか非効率的に実行されるかを判断する良い方法ではありません。 (someCondition ? true : false)のような構造が(someCondition)よりもパフォーマンスが悪いJVM実装と、同じように動作するJVM実装がある可能性があります。

4
supercat

IntelliJでは、コードをコンパイルし、自動的に逆コンパイルされるクラスファイルを開きました。結果は次のとおりです。

boolean val = foo == bar && foo1 != bar;

つまり、Javaコンパイラが最適化します。

1
Codo

synthesize 以前の回答で提供された優れた情報を提供したいと思います。

OracleのjavacとEclipseのecjが次のコードで何を行うかを見てみましょう。

_boolean  valReturn(int a, int b) { return a == b; }
boolean condReturn(int a, int b) { return a == b ? true : false; }
boolean   ifReturn(int a, int b) { if (a == b) return true; else return false; }

void  valVar(int a, int b) { boolean c = a == b; }
void condVar(int a, int b) { boolean c = a == b ? true : false; }
void   ifVar(int a, int b) { boolean c; if (a == b) c = true; else c = false; }
_

(コードを少し簡略化しました-2つではなく1つの比較ですが、以下で説明するコンパイラーの動作は、わずかに異なる結果を含めて、基本的に同じです。)

私はjavacとecjでコードをコンパイルしてから、Oracleのjavapで逆コンパイルしました。

これがjavacの結果です(私はjavac 9.0.4と11.0.2を試しました-まったく同じコードを生成します):

_boolean valReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean condReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean ifReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

void valVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void condVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void ifVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     10
     5: iconst_1
     6: istore_3
     7: goto          12
    10: iconst_0
    11: istore_3
    12: return
_

そして、ecj(バージョン3.16.0)の結果は次のとおりです。

_boolean valReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

boolean condReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean ifReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

void valVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void condVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void ifVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     10
     5: iconst_1
     6: istore_3
     7: goto          12
    10: iconst_0
    11: istore_3
    12: return
_

6つの関数のうち5つについては、両方のコンパイラがまったく同じコードを生成します。 唯一の違いvalReturnにあります。javacはgotoireturnに生成しますが、ecjはireturnを生成します。 condReturnの場合、どちらもgotoireturnに生成します。 ifReturnの場合、どちらもireturnを生成します。

それは、コンパイラの1つがこれらのケースの1つ以上を最適化することを意味しますか? javacはifReturnコードを最適化するとは思うかもしれませんが、valReturncondReturnを最適化できませんが、ecjはifReturnvalReturnを最適化します、ただしcondReturnを最適化できません。

しかし、私はそれが本当だとは思いません。 Javaソースコードコンパイラは基本的にコードを最適化しません。コードを最適化するコンパイラが行うコードはJITです(ジャストインタイム)コンパイラ(バイトコードをマシンコードにコンパイルするJVMの部分)、およびバイトコードが比較的単純な最適化されていません

一言で言えば、いいえ、Javaソースコードコンパイラは、実際には何も最適化しないため、このケースを最適化しません。仕様で要求されていることを実行しますが、それ以上は実行しません。javacそして、ecj開発者は、これらのケースに対して(おそらく多かれ少なかれ恣意的な理由で)わずかに異なるコード生成戦略を選択しました。

詳細については、 theseStack Overflowquestions を参照してください。

(適例:両方のコンパイラーは現在、_-O_フラグを無視しています。ecjオプションは明示的にそのように言っています:-O: optimize for execution time (ignored)。javacはフラグに言及しなくなっただけで、無視します。)