Javaに末尾再帰の最適化がないのはなぜですか?


92

私が読んだことから:理由は、継承があるためにどのメソッドが実際に呼び出されるかを決定するのは簡単ではないからです。

しかし、なぜJavaには少なくとも静的メソッドの末尾再帰最適化がなく、コンパイラで静的メソッドを呼び出す適切な方法を強制しないのですか?

Javaが末尾再帰をまったくサポートしないのはなぜですか?

ここに問題があるかどうかはまったくわかりません。


JörgW Mittag 1が説明したように提案された複製について:

  • もう1つの質問は、TCOに関する質問です。これはTREに関する質問です。TREはTCOよりもはるかに単純です。
  • また、別の質問では、JVMがJVMにコンパイルしたい言語実装にどのような制限を課しているのかを尋ねます。この質問は、JVMによって制限されないJavaについて質問します。 Javaを設計する同じ人々。
  • 最後に、JVMにはメソッド内GOTOがあり、TREに必要なのはそれだけであるため、JVMにはTREに関する制限さえありません。

1作成されたポイントを呼び出すためにフォーマットが追加されました。


26
引用するエリックリペットを「特長は安くはありません。彼らは非常に高価であり、彼らは唯一、自分のコストを正当化してはならない、彼らは我々がその予算で行っている可能性百の他の機能をしていないの機会費用を正当化しなければなりません。」Javaは、一部はC / C ++開発者にアピールするように設計されており、これらの言語では末尾再帰の最適化は保証されていません。
ドーバル

5
私が間違っている場合は修正しますが、Eric LippertはC#のデザイナーであり、末尾再帰最適化を持っていますか?
情報

1
彼はC#コンパイラチームです。
ドーバル

10
私が理解していることから、おそらく特定の条件が満たされていれば、JITはそれを行うかもしれません。したがって、実際にはそれに頼ることはできません。しかし、C#がC#を持っているかどうかは、エリックのポイントにとって重要ではありません。
ドーバル

4
@InstructedA少し深く掘り下げると、32ビットJITコンパイラーでは決して行われていないことがわかります。64ビットJITは、多くの点で新しくスマートです。さらに新しい実験的なコンパイラ(32ビットと64ビットの両方)はさらに賢く、明示的に要求しないILの末尾再帰最適化をサポートします。考慮する必要があるもう1つのポイントがあります-JITコンパイラーにはあまり時間がありません。それらは速度のために大きく最適化されています-C ++でコンパイルするのに数時間かかるかもしれないアプリケーションは、せいぜい(少なくとも部分的に)数百ミリ秒でILからネイティブに移行する必要があります。
ルアーン

回答:


132

このビデオでBrian Goetz(OracleのJava Language Architect)が説明したように:

jdkクラス[...]には、jdkライブラリコードと呼び出しコード間のスタックフレームのカウントに依存して、誰が呼び出しているのかを判断するセキュリティに敏感なメソッドがいくつかあります。

スタック上のフレーム数を変更すると、これが壊れてエラーが発生します。彼はこれが馬鹿げた理由だと認めているため、JDK開発者はこのメカニズムを置き換えました。

彼はさらに、それは優先順位ではないが、そのテール再帰

最終的に完了します。

NBこれはHotSpotおよびOpenJDKに適用されますが、他のVMは異なる場合があります。


7
このようなかなり切り取った乾燥した答えがあることに驚いています!しかし、それは本当に答えのように見えます-技術的な理由により、今はまだ行われていないので、実装するのに十分重要であると誰かが判断するのを待っています。
ブライアン

1
回避策を実装しないのはなぜですか?引数の新しい値でメソッド呼び出しの先頭にジャンプするunder-the-covers label-gotoのように?これはコンパイル時の最適化であり、スタックフレームをシフトしたり、セキュリティ違反を引き起こす必要はありません。
ジェームズワトキンス

2
あなたや他の誰かがより深く掘り下げて、それらの「セキュリティに敏感な方法」が何であるかを具体的に提供できれば、私たちにとってはるかに良いでしょう。ありがとうございました!
情報

3
@InstructedAは-を参照securingjava.com/chapter-three/chapter-three-6.html Javaセキュリティマネージャシステムは、Java 2のリリース年頃働いたかの詳細な記述が含まれている
ジュール・

3
回避策の1つは、別のJVM言語を使用することです。たとえば、Scalaは実行時ではなくコンパイラーでこれを行います。
ジェームスムーア

24

Javaには、ほとんどの命令型言語にはない同じ理由で、末尾呼び出しの最適化がありません。命令型ループは言語の優先スタイルであり、プログラマは末尾再帰を命令型ループに置き換えることができます。スタイルの問題として使用が推奨されていない機能にとって、複雑さは価値がありません。

プログラマーがFPスタイルで他の命令型言語で記述したいことは、コンピューターがGHzではなくコアでスケーリングを開始してから10年ほどで流行しました。今でも、それほど人気はありません。命令ループを職場での末尾再帰に置き換えることを提案した場合、コードレビューアの半分は笑い、残りの半分は混乱した外観になります。関数型プログラミングの場合でも、高階関数などの他の構造がきれいに適合しない場合を除き、通常は末尾再帰を避けます。


36
この答えは正しくないようです。末尾再帰が厳密に必要でないからといって、それが役に立たないというわけではありません。再帰的な解決策は反復よりも理解しやすいことがよくありますが、末尾呼び出しがないため、再帰的なアルゴリズムはスタックを爆破する大きな問題サイズに対して不正確になります。これは、パフォーマンスではなく、正確さに関するものです(簡単にするために、トレーディングスピードは価値があります)。正解として、テールコールの欠如は、命令型の偏見ではなく、スタックトレースに依存する奇妙なセキュリティモデルによるものです。
アモン

4
ジェームズ・ゴスリン@amonかつて彼は、Javaに機能を追加しないだろうと述べた複数の人がそれを要求しない限り、だけにして、彼は考えを検討し、それを。そのため、答えの一部が本当にforループを常に使用できる」場合(そして、同様にファーストクラスの関数とオブジェクトの場合)であったとしても、私は驚かないでしょう。私はそれを「命令的な偏見」と呼ぶほどには行きませんが、おそらく最大の懸念がJavaの速度とジェネリックの欠如であった1995年に非常に高い需要があったとは思いません。
ドーバル

7
@amon、セキュリティモデルは、既存の言語にTCOを追加しない正当な理由として私を攻撃しますが、そもそもTCOを言語に設計しない理由としては不十分です。マイナーな舞台裏の機能を含めるために、プログラマーに見える主要な機能を捨てないでください。「Java 8にTCOがないのはなぜですか」は、「Java 1.0にTCOがないのはなぜですか?」私は後者に答えていました。
カールビーレフェルト

3
@rwongでは、再帰関数を反復関数に変換できます。(もし可能なら、私はここに例を書いた)
アラン

3
@ZanLynxそれは問題の種類に依存します。たとえば、FPとは関係なく、ビジターパターンはTCOの恩恵を受けることができます(構造とビジターの詳細に依存します)。トランポリンを使用した変換は(問題に応じて)少し自然に見えますが、ステートマシンを経由することも同様です。また、技術的にはループを使用してツリーを実装できますが、コンセンサスは再帰がはるかに自然であると考えています(DFSに似ていますが、この場合はTCOでもスタック制限による再帰を避けることができます)。
マチェイピエチョトカ

5

JVMには(一部のvtableのメソッドなどの静的に未知の関数ポインターへの)テールコール用のバイトコードがないため、Javaにはトールコール最適化がありません。

社会的な(そしておそらく技術的な)理由から、JVMに新しいバイトコード操作を追加する(JVMの以前のバージョンと互換性がなくなる)のは、JVM仕様の所有者にとって非常に難しいようです。

JVM仕様に新しいバイトコードを追加しない技術的理由には、実際のJVM実装が非常に複雑なソフトウェア部分であるという事実が含まれます(たとえば、多くのJIT最適化のため)。

未知の関数のテールコールでは、現在のスタックフレームを新しいものに置き換える必要があり、その操作はJVM内に存在する必要があります(コンパイラを生成するバイトコードを変更するだけではありません)。


17
問題はテールコールではなく、テール再帰です。そして、質問はJVMバイトコード言語に関するものではなく、Javaプログラミング言語に関するものであり、これはまったく異なる言語です。Scalaは(特に)JVMバイトコードにコンパイルし、末尾再帰を排除します。JVMでのScheme実装には、完全な適切なTail-Callがあります。Java 7(JVM Spec、第3版)は新しいバイトコードを追加しました。IBMのJ9 JVMは、特別なバイトコードを必要とせずにTCOを実行します。
ヨルグWミットタグ

1
@JörgWMittag:それは本当ですが、それでは、Javaプログラミング言語が適切なテールコールを持たないのは本当かどうか疑問に思います。Javaプログラミング言語がないことを述べることがより正確かもしれない持っている、それスペックの義務でその何で、適切な末尾再帰を持っています。(つまり、仕様内の何かが実際に実装がテール呼び出しを排除することを禁じているかどうかは
わかり

4

言語に末尾呼び出し(再帰またはその他)を行うための特別な構文があり、コンパイラーが末尾呼び出しが要求されたが生成できない場合にスコークしない限り、「オプションの」末尾呼び出しまたは末尾再帰の最適化は、 1つのマシンでは100バイト未満のスタックが必要ですが、別のマシンでは100,000,000バイトを超えるスタックが必要になる場合があります。このような違いは、単に定量的ではなく定性的であると見なされる必要があります。

マシンのスタックサイズが異なることが予想されるため、コードは常に1台のマシンで動作し、別のマシンではスタックを破壊する可能性があります。ただし、一般的に、スタックが人為的に制限されている場合でも1台のマシンで動作するコードは、「通常の」スタックサイズのすべてのマシンで動作する可能性があります。ただし、1,000,000の深さを再帰するメソッドが1台のマシンでテールコール最適化され、別のマシンでは最適化されない場合、前者のマシンでの実行は、スタックが異常に小さい場合でも機能し、スタックが異常に大きい場合でも後者で失敗する可能性があります。


2

主にJavaでテールコール再帰が使用されないのは、スタックトレースが変更されるため、プログラムのデバッグがはるかに困難になるためです。Javaの主な目標の1つは、プログラマーがコードを簡単にデバッグできるようにすることであり、特に高度なオブジェクト指向プログラミング環境でスタックトレースを行うには不可欠です。代わりに反復を使用できるため、言語委員会は、末尾再帰を追加する価値がないと考えていたに違いありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.