JVMがまだテールコール最適化をサポートしていないのはなぜですか?


95

does-the-jvm-prevent-tail-call-optimizationsの 2年後、プロトタイプの 実装があり、MLVMはしばらくの間、この機能を「proto 80%」としてリストしています。

JVMで言及されているように、テールコールのサポートにSun / Oracleの側から積極的な関心はありませんか、それとも、テールコールが「すべての機能優先度リストで2番目に来ると運命づけられている [...]」だけです。言語サミット

誰かがMLVMビルドをテストし、それがどれほどうまく機能するかについての印象を(もしあれば)共有できたら、私は本当に興味があります。

更新: Avianのような一部のVM は、問題なく適切な末尾呼び出しをサポートしています。


14
OracleのSunの人々の報告脱出して、私がいることを期待していない任意の明示的に:(オラクルから言わない限り、現在のプロジェクトが継続
するThorbjörnRavnアンデルセン

16
あなたが受け入れた答えは完全に間違っていることに注意してください。テールコールの最適化とOOPの間に基本的な競合はありません。もちろん、OCamlやF#などのいくつかの言語にはOOPとTCOの両方があります。
JD

2
まあ、OCamlとF#OOP言語を呼び出すことは、そもそも悪い冗談です。しかし、はい、OOPとTCOはあまり共通していません。ただし、ランタイムが最適化されているメソッドが他の場所でオーバーライド/サブクラス化されていないことを確認する必要があるという事実を除きます。
soc

5
+1 Cのバックグラウンドから来て、私は常にTCOが現代のJVMで与えられていると思いました。実際に確認することは
思いがけず

2
@soc:「ランタイムは、最適化されているメソッドが他の場所でオーバーライド/サブクラス化されていないことを確認する必要があるという事実を除いて」。あなたの「事実」は完全にナンセンスです。
JD 2012年

回答:


32

Javaコードの診断:Javaコードのパフォーマンスの向上alt)では、JVMが末尾呼び出しの最適化をサポートしていない理由を説明しています。

しかし、末尾再帰関数を単純なループに自動的に変換する方法はよく知られていますが、Java仕様では、この変換を行う必要はありません。おそらく、それが要件ではない理由の1つは、一般に、オブジェクト指向言語では変換を静的に行うことができないためです。代わりに、末尾再帰関数から単純ループへの変換は、JITコンパイラーによって動的に行われる必要があります。

次に、変換されないJavaコードの例を示します。

したがって、リスト3の例が示すように、静的コンパイラーが言語のセマンティクスを維持しながら、Javaコードでテール再帰の変換を実行することは期待できません。代わりに、JITによる動的コンパイルに依存する必要があります。JVMに応じて、JITはこれを行う場合と行わない場合があります。

次に、JITがこれを行うかどうかを判断するために使用できるテストを提供します。

当然、これはIBMの論文なので、プラグが含まれています。

このプログラムをいくつかのJava SDKで実行したところ、驚くべき結果が得られました。Sunのバージョン1.3のHotspot JVMで実行すると、Hotspotが変換を実行しないことがわかります。デフォルトの設定では、マシンのスタックスペースが1秒未満で使い果たされます。一方、バージョン1.3用のIBMのJVMは問題なく動作し、コードがこのように変換されることを示しています。


62
FWIW、彼が示唆するように、末尾呼び出しは自己再帰関数だけではありません。末尾呼び出しは、末尾の位置に表示される関数呼び出しです。それらはselfへの呼び出しである必要はなく、静的に既知の場所への呼び出しである必要もありません(たとえば、それらは仮想メソッド呼び出しである場合があります)。彼が説明する問題は、テールコールの最適化が一般的なケースで適切に行われれば問題ではなく、その結果、彼の例はテールコールをサポートするオブジェクト指向言語(OCamlやF#など)で完全に機能します。
JD、

3
「JITコンパイラーによって動的に実行する必要があります」。つまり、Javaコンパイラーではなく、JVM自体によって実行する必要があります。しかし、OPはJVMについて尋ねています。
Raedwald、2011年

11
「一般に、変換はオブジェクト指向言語で静的に行うことはできません。」これはもちろん引用ですが、そのような言い訳を見るたびに、数値について質問したいと思います。実際のところ、ほとんどの場合、コンパイル時にそれが確立できたとしても驚かないからです。
greenoldman

5
引用記事へのリンクは壊れていますが、Googleではキャッシュしています。さらに重要なのは、著者の推論に誤りがあることです。与えられた例は、可能性が末尾呼び出しは、静的を使用して、最適化することがないだけコンパイラが挿入された場合、単に動的コンパイルをinstanceofどうかを確認しthisているExampleオブジェクト(よりむしろサブクラスのをExample)。
Alex D


30

過去にJavaにTCOを実装しない(そして、難しいと思われる)理由の1つは、JVMのアクセス許可モデルはスタックに依存するため、末尾呼び出しでセキュリティの側面を処理する必要があるためです。

これはClementsとFelleisen [1] [2]による障害ではないことが示されていると思います。質問で言及されているMLVMパッチも同様に対処すると確信しています。

これはあなたの質問に答えないことに気づきました。興味深い情報を追加するだけです。

  1. http://www.ccs.neu.edu/scheme/pubs/esop2003-cf.pdf
  2. http://www.ccs.neu.edu/scheme/pubs/cf-toplas04.pdf

1
+1。ブライアンゲッツのこのプレゼンテーションの最後に質問/回答を聞くyoutube.com/watch?v=2y5Pv4yN0b0&t=3739
mcoolive 2017年

15

おそらくあなたはすでにこれを知っているかもしれませんが、Java言語は実際にスタックトレースをプログラマーに公開しているため、この機能は思ったほど簡単ではありません。

次のプログラムを検討してください。

public class Test {

    public static String f() {
        String s = Math.random() > .5 ? f() : g();
        return s;
    }

    public static String g() {
        if (Math.random() > .9) {
            StackTraceElement[] ste = new Throwable().getStackTrace();
            return ste[ste.length / 2].getMethodName();
        }
        return f();
    }

    public static void main(String[] args) {
        System.out.println(f());
    }
}

これには「末尾呼び出し」がありますが、最適化されていない可能性があります。(それは場合はされて最適化されたプログラムのセマンティクスがそれに依存しているため、それはまだ全体の呼び出しスタックの記帳が必要です。)

基本的に、これは下位互換性を維持しながらサポートするのが難しいことを意味します。


17
「プログラムのセマンティクスがコールスタックに依存しているため、コールスタック全体の簿記が必要である」という考えの誤りを発見しました。:-)それは新しい「抑制された例外」のようなものです。そのようなものに依存しているプログラムは壊れるに違いありません。私の意見では、プログラムの動作は完全に正しいです。スタックフレームを破棄することがテールコールのすべてです。
SOC

4
@Marco、しかし、ほぼすべてのメソッドが例外をスローする可能性があり、そこからコールスタック全体が利用できるようにバインドされていますよね?さらに、gこの場合間接的に呼び出すメソッドを事前に決定することはできません...たとえば、ポリモーフィズムとリフレクションについて考えてみてください。
aioobe

2
これは、Java 7にARMが追加されたことによる副作用です。これは、上記で示したようなものに依存できない例です。
soc

6
「言語がコールスタックを公開しているため、これを実装するのが難しくなります」:ソースコードが示すgetStackTrace()メソッドから返されたスタックトレースが、メソッドからx()呼び出され、メソッドが呼び出されたことy()も示されている必要x()がありy()ますか?ある程度の自由があれば、本当の問題はありません。
Raedwald、2011年

8
これは、「すべてのスタックフレームを提供する」から「すべてのアクティブなスタックフレームを提供し、末尾の呼び出しによって不要になったものを除外する」まで、単一のメソッドの仕様を説明するだけの問題です。さらに、テールコールを有効にするかどうかに関係なく、コマンドラインスイッチまたはシステムプロパティにすることができます。
Ingo

12

Javaは、あなたが想像できる最も機能的な言語です(まあ、そうですね、多分そうではありません!)が、これはScalaなどのJVM言語にとっては大きな利点です。

私の見解では、JVMを他の言語のプラットフォームにすることは、Sunの優先順位リストの最上位にあるように思われることは一度もなかったようです。


16
@Thorbjørn-特定のプログラムが有限の時間内に停止するかどうかを予測するプログラムを作成しました。それは私にかかった年齢を
oxbow_lakes 2010

3
最初に使用したBASICには関数がなく、GOSUBとRETURNがありました。LOLCODEも非常に機能的だとは思いません(2つの意味でそれを理解できます)。
David Thornley、2010

1
@David、function!=には関数があります。
–ThorbjørnRavn Andersen、2010

2
@ThorbjørnRavn Andersen:いいえ、でもそれは必要条件だと思いませんか?
David Thornley、2010

3
「JVMを他の言語のプラットフォームにすることは、Sunの優先リストの最上位にあるように思われたことはありません。」彼らは、JVMを動的言語のプラットフォームにすることに、関数型言語の場合よりもはるかに多くの努力を注ぎました。
JD
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.