web-dev-qa-db-ja.com

最適化中にJavaインラインメソッド)でしょうか?

JVM/javacは十分に賢いのだろうか

// This line...
string a = foo();

string foo()
{
  return bar();
}

string bar()
{
  return some-complicated-string computation;
}

string a = bar();

または、リリースケースでfoo()への不要な呼び出しを削除します(到達不能コードのため):

string a = foo(bar());

// bar is the same
...

string foo(string b)
{
  if (debug) do-something-with(b);
}

最初の例では「はい」、2番目の例では「よくわからない」と感じていますが、それを確認するためのポインタやリンクを誰かに教えてもらえますか?

27
Schultz9999

javacは、バイトコードを生成した元のJavaプログラムを忠実に表現したバイトコードを表示します(最適化できる特定の状況を除く:定数畳み込みおよびデッドコード除去)。ただし、JVMがを使用する場合、最適化はJVMによって実行される場合があります。 JITコンパイラ。

最初のシナリオでは、JVMがインライン化をサポートしているように見えます(メソッドここ および ここ を参照) = JVMのインライン化の例の場合)。

javac自体によって実行されているメソッドインライン化の例は見つかりませんでした。いくつかのサンプルプログラム(質問で説明したものと同様)をコンパイルしようとしましたが、finalの場合でも、メソッドを直接インライン化するものはなかったようです。この種の最適化は、javacではなく、JVMのJITコンパイラによって行われるように思われます。 Methodshere で説明されている「コンパイラ」は、javacではなくHotSpotJVMのJITコンパイラのようです。

私が見ることができることから、javacデッドコード除去(2番目のケースの例を参照)と定数畳み込み。定数畳み込みでは、コンパイラーは定数式を事前計算し、実行時に計算を実行する代わりに、計算された値を使用します。例えば:

public class ConstantFolding {

   private static final int a = 100;
   private static final int b = 200;

   public final void baz() {
      int c = a + b;
   }
}

次のバイトコードにコンパイルされます。

Compiled from "ConstantFolding.Java"
public class ConstantFolding extends Java.lang.Object{
private static final int a;

private static final int b;

public ConstantFolding();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   return

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

バイトコードには、aloadgetfieldsとiaddの代わりにsipush 300があることに注意してください。 300は計算値です。これは、private final変数にも当てはまります。 abが静的でない場合、結果のバイトコードは次のようになります。

Compiled from "ConstantFolding.Java"
public class ConstantFolding extends Java.lang.Object{
private final int a;

private final int b;

public ConstantFolding();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   aload_0
   5:   bipush  100
   7:   putfield    #2; //Field a:I
   10:  aload_0
   11:  sipush  200
   14:  putfield    #3; //Field b:I
   17:  return

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

ここでも、sipush 300が使用されます。

2番目のケース(デッドコード除去)では、次のテストプログラムを使用しました。

public class InlineTest {

   private static final boolean debug = false;

   private void baz() {
      if(debug) {
         String a = foo();
      }
   }

   private String foo() {
      return bar();
   }

   private String bar() {
      return "abc";
   }
}

これにより、次のバイトコードが得られます。

Compiled from "InlineTest.Java"
public class InlineTest extends Java.lang.Object{
private static final boolean debug;

public InlineTest();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   return

private void baz();
  Code:
   0:   return

private Java.lang.String foo();
  Code:
   0:   aload_0
   1:   invokespecial   #2; //Method bar:()Ljava/lang/String;
   4:   areturn

private Java.lang.String bar();
  Code:
   0:   ldc #3; //String abc
   2:   areturn

}

ご覧のとおり、fooブロック内のコードは事実上「デッド」であるため、bazではifはまったく呼び出されません。

Sun(現在はOracle)のHotSpot JVMは、バイトコードの解釈とJITコンパイルを組み合わせたものです。バイトコードがJVMに提示されると、コードは最初に解釈されますが、JVMはバイトコードを監視し、頻繁に実行される部分を選択します。これらの部分をネイティブコードに変換して、実行速度を上げます。あまり頻繁に使用されないバイトコードの場合、このコンパイルは実行されません。コンパイルにはオーバーヘッドがあるため、これも同様です。ですから、それは本当にトレードオフの問題です。すべてのバイトコードをネイティブコードにコンパイルすることにした場合、コードの起動遅延が非常に長くなる可能性があります。

JVMは、バイトコードの監視に加えて、バイトコードを解釈およびロードするときにバイトコードの静的分析を実行して、さらに最適化を実行することもできます。

JVMが実行する特定の種類の最適化を知りたい場合は、Oracleの このページ が非常に役立ちます。 HotSpotJVMで使用されるパフォーマンス手法について説明します。

29
Vivin Paliath

JVMはおそらくインラインになります。一般に、人間が読みやすいように最適化するのが最善です。 JVMにランタイム最適化を実行させます。

JVMエキスパート Brian Goetzによるとfinalはインライン化されるメソッドに影響を与えません。

3
Steve Kuo

同じクラスファイルで、javacはstaticfinalをインライン化できます(他のクラスファイルはインライン化された関数を変更する可能性があります)

ただし、JITはコードについて詳しく知っているため、より多くの最適化を行うことができます(余分な境界の削除やnullチェックのインライン化など)。

2
ratchet freak

「高度に最適化された」JITコンパイラーは両方のケースをインライン化します(そして、@ Mysticialでは、さまざまな形式のトリックを使用することで、いくつかの多型ケースをインライン化することさえあります)。

メソッドをfinalにするなど、いくつかのトリックを行うことで、インライン化の可能性を高めることができます。

javacは、主にいくつかの条件付きコンパイルパラダイムを支援することを目的とした、主にfinal/privateメソッドのいくつかのプリミティブインライン化を行います。

2
Hot Licks

Bar()で例外をスローし、スタックトレースを出力すると、呼び出しのパス全体が表示されます... Javaすべてを尊重すると思います。

2番目のケースも同じです。デバッグはシステムの変数であり、C++のように定義されていないため、事前に評価する必要があります。

0
yoprogramo