JVMでランタイムコールスタックサイズを増やす方法を知るために、この質問をしました。これに対する答えがあります。また、大規模なランタイムスタックが必要な状況をJavaがどのように処理するかに関連する多くの有用な答えとコメントもあります。回答の要約で質問を拡張しました。
もともと、JVMスタックサイズを増やして、プログラムがStackOverflowError
なしで実行されるようにしました。
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
対応する構成設定は、十分な大きさのJava -Xss...
コマンドラインフラグです。上記のプログラムTT
では、OpenJDKのJVMで次のように動作します。
$ javac TT.Java
$ Java -Xss4m TT
答えの1つは、-X...
フラグが実装に依存していることも指摘しています。私が使っていた
Java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
また、1つのスレッドにのみ大きなスタックを指定することもできます(回答の1つを参照)。 Java -Xss...
よりも、これを必要としないスレッドのメモリを無駄にしないようにすることをお勧めします。
上記のプログラムが正確に必要とするスタックの大きさに興味があったので、それを実行してn
を増やしました:
fact(1 << 15)
に十分ですfact(1 << 17)
に十分ですfact(1 << 18)
に十分ですfact(1 << 19)
に十分ですfact(1 << 20)
に十分ですfact(1 << 21)
に十分ですfact(1 << 22)
に十分ですfact(1 << 23)
に十分ですfact(1 << 24)
に十分ですfact(1 << 25)
に十分です上記の数値から、Javaは上記の関数に対してスタックフレームごとに約16バイトを使用しているようで、これは妥当です。
上記の列挙には、スタック要件が決定的ではないため、十分であるの代わりに十分であるが含まれています:同じソースファイルと同じ-Xss...
で複数回実行すると成功することがあり、時々StackOverflowError
を生成します。例えば。 1 << 20では、-Xss18m
は10回のうち7回の実行で十分であり、-Xss19m
も常に十分ではありませんでしたが、-Xss20m
で十分でした(100回のうち100回の実行で)。ガベージコレクション、JITの起動、または他の何かがこの非決定的な動作を引き起こしますか?
StackOverflowError
に出力されるスタックトレース(および、おそらく他の例外にも表示される)には、ランタイムスタックの最新の1024要素のみが表示されます。以下の回答は、到達した正確な深さをカウントする方法を示しています(1024よりもはるかに大きい場合があります)。
回答した多くの人々は、同じアルゴリズムの代替でスタックの少ない実装を検討することは良い安全なコーディング手法であると指摘しています。一般に、再帰関数のセットから反復関数への変換が可能です(たとえば、ランタイムスタックではなくヒープに入力されるStack
オブジェクトを使用)。この特定のfact
関数の場合、変換は非常に簡単です。私の反復バージョンは次のようになります。
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
参考までに、上記の反復ソリューションが示すように、Java組み込み型fact
がオーバーフローするため、long
関数は65を超える(実際には20を超える)数値の正確な階乗を計算できません。 fact
の代わりにBigInteger
を返すようにlong
をリファクタリングすると、大きな入力に対しても正確な結果が得られます。
うーん...それは私のために動作し、999MBよりはるかに少ないスタックで:
> Java -Xss4m Test
0
(Windows JDK 7、ビルド17.0-b05クライアントVM、およびLinux JDK 6-投稿したものと同じバージョン情報)
スタックトレースの繰り返し行で「1024の深さ」を計算したと思いますか?
明らかに、Throwableのスタックトレース配列の長さは1024に制限されているようです。次のプログラムを試してください。
public class Test {
public static void main(String[] args) {
try {
System.out.println(fact(1 << 15));
}
catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was " +
e.getStackTrace().length);
}
}
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
}
スレッドスタックサイズを試してみたい場合は、Hotspot JVMの-Xssオプションを確認してください。 JVMへの-Xパラメーターはディストリビューション固有のIIRCであるため、非ホットスポットVMでは異なる場合があります。
ホットスポットでは、サイズを16 MBにしたい場合、Java -Xss16M
のように見えます。
渡すことができるディストリビューション固有のJVMパラメーターをすべて表示する場合は、Java -X -help
と入力します。これが他のJVMでも同じように機能するかどうかはわかりませんが、Hotspot固有のパラメーターをすべて出力します。
それが価値があることのために-Javaでの再帰メソッドの使用を制限することをお勧めします。それらを最適化するのはそれほど素晴らしいことではありません-JVMが末尾再帰をサポートしていない( JVMは末尾呼び出しの最適化を妨げますか? を参照)。上記の階乗コードをリファクタリングして、再帰的なメソッド呼び出しの代わりにwhileループを使用してみてください。
プロセス内のスタックのサイズを制御する唯一の方法は、新しいThread
を開始することです。ただし、-Xss
パラメータを使用して自己呼び出しサブJavaプロセスを作成することで制御することもできます。
public class TT {
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(null, null, "TT", 1000000) {
@Override
public void run() {
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
};
t.start();
t.join();
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
}
このオプションを追加
--driver-Java-options -Xss512m
spark-submitコマンドにこの問題を修正します。
すべての正気なアプローチを避けたいので、賢明な解決策を与えるのは難しいです。 1行のコードをリファクタリングするのが賢明な解決策です。
注:-Xssを使用すると、すべてのスレッドのスタックサイズが設定されるため、非常に悪い考えです。
別のアプローチは、次のようにコードを変更するバイトコード操作です。
public static long fact(int n) {
return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1);
}
n> 127のすべての答えが0の場合、ソースコードを変更しないでください。
私は Anagram excersize を行いました。これは Count Change の問題ですが、50 000単位(コイン)でした。私は 繰り返し実行できるかどうかわかりません 、気にしません。 -xssオプションは効果がなかったことを知っています-1024スタックフレームの後で常に失敗しました(scalaがJavaまたはprintStackTraceの制限への配信がうまくいかない可能性があります。知っている)。とにかく説明されているように、これは悪いオプションです。アプリ内のすべてのスレッドが巨大になることは望ましくありません。ただし、新しいスレッド(スタックサイズ)でいくつかの実験を行いました。これは確かに機能しますが、
def measureStackDepth(ss: Long): Long = {
var depth: Long = 0
val thread: Thread = new Thread(null, new Runnable() {
override def run() {
try {
def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1}
println("fact = " + sum(ss * 10))
} catch {
case e: StackOverflowError => // eat the exception, that is expected
}
}
}, "deep stack for money exchange", ss)
thread.start()
thread.join()
depth
} //> measureStackDepth: (ss: Long)Long
for (ss <- (0 to 10)) println("ss = 10^" + ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong) )
//> fact = 10
//| (ss = 10^0 allows stack of size ,11)
//| fact = 100
//| (ss = 10^1 allows stack of size ,101)
//| fact = 1000
//| (ss = 10^2 allows stack of size ,1001)
//| fact = 10000
//| (ss = 10^3 allows stack of size ,10001)
//| (ss = 10^4 allows stack of size ,1336)
//| (ss = 10^5 allows stack of size ,5456)
//| (ss = 10^6 allows stack of size ,62736)
//| (ss = 10^7 allows stack of size ,623876)
//| (ss = 10^8 allows stack of size ,6247732)
//| (ss = 10^9 allows stack of size ,62498160)
スレッドに割り当てられるスタックが指数関数的に増えると、スタックが指数関数的にさらに深くなることがわかります。
変だ! 1 << 15の深さの再帰 ??? !!!!を生成したいと言っています。
私はそれを試さないことをお勧めします。スタックのサイズは2^15 * sizeof(stack-frame)
になります。スタックフレームのサイズはわかりませんが、2 ^ 15は32.768です。かなり...まあ、それが1024(2 ^ 10)で停止する場合、実際の設定よりも2 ^ 5倍、つまり32倍大きくする必要があります。
他のポスターは、記憶を増やす方法と、あなたが電話を覚えることができると指摘しました。多くのアプリケーションでは、スターリングの式を使用して大きなnを近似できることをお勧めします。メモリフットプリントがほとんどなく、非常に高速です。
この投稿をご覧ください。機能とコードの分析があります。
http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/