再帰的なメソッド呼び出しにより、kotlinでStackOverFlowErrorが発生するが、Javaでは発生しない


14

私はJavaとKotlinに2つのほとんど同じコードがあります

Java:

public void reverseString(char[] s) {
    helper(s, 0, s.length - 1);
}

public void helper(char[] s, int left, int right) {
    if (left >= right) return;
    char tmp = s[left];
    s[left++] = s[right];
    s[right--] = tmp;
    helper(s, left, right);
}

コトリン:

fun reverseString(s: CharArray): Unit {
    helper(0, s.lastIndex, s)
}

fun helper(i: Int, j: Int, s: CharArray) {
    if (i >= j) {
        return
    }
    val t = s[j]
    s[j] = s[i]
    s[i] = t
    helper(i + 1, j - 1, s)
}

Javaコードは巨大な入力でテストに合格しますが、kotlinコードは、kotlin の関数の前にキーワードをStackOverFlowError追加しない限り、原因となります。tailrechelper

この関数がjavaで機能する理由と、kolinで機能するtailrecが、kotlinでは機能しない理由を知りたいのtailrecですが?

PS: 私は何をtailrecすべきか知っています


1
これらをテストしたところ、Javaバージョンは約29500までの配列サイズで機能しましたが、Kotlinバージョンは約18500で停止しました。これは大きな違いですが、それほど大きなものではありません。これが大きな配列で機能するために必要な場合、唯一の良い解決策は、を使用するtailrecか、再帰を回避することです。使用可能なスタックサイズは、実行間、JVMとセットアップ間、およびメソッドとそのパラメーターによって異なります。しかし、もしあなたが純粋な好奇心から完全に正当な理由を求めているなら、私にはわかりません。おそらくバイトコードを見る必要があるでしょう。
19

回答:


7

私は、この関数がで働く理由を知りたいのjavaともにkotlintailrecはなく、中にkotlinなしtailrec

簡単に言えば、KotlinメソッドはJAVAメソッドよりも「重い」ためです。すべての呼び出しで、「挑発」する別のメソッドを呼び出しますStackOverflowError。したがって、以下の詳細な説明を参照してください。

対応するJavaバイトコード reverseString()

KotlinJAVAで対応するメソッドのバイトコードをチェックしました。

JavaのKotlinメソッドのバイトコード

...
public final void reverseString(@NotNull char[] s) {
    Intrinsics.checkParameterIsNotNull(s, "s");
    this.helper(0, ArraysKt.getLastIndex(s), s);
}

public final void helper(int i, int j, @NotNull char[] s) {
    Intrinsics.checkParameterIsNotNull(s, "s");
    if (i < j) {
        char t = s[j];
        s[j] = s[i];
        s[i] = t;
        this.helper(i + 1, j - 1, s);
    }
}
...

JAVAのJAVAメソッドのバイトコード

...
public void reverseString(char[] s) {
    this.helper(s, 0, s.length - 1);
}

public void helper(char[] s, int left, int right) {
    if (left < right) {
        char temp = s[left];
        s[left++] = s[right];
        s[right--] = temp;
        this.helper(left, right, s);
    }
}
...

したがって、2つの主な違いがあります。

  1. Intrinsics.checkParameterIsNotNull(s, "s")Kotlinバージョンhelper()では、それぞれに対して呼び出されます。
  2. JAVAメソッドの左と右のインデックスは増加しますが、Kotlinでは、再帰呼び出しごとに新しいインデックスが作成されます。

それでは、Intrinsics.checkParameterIsNotNull(s, "s")単独でどのように動作に影響するかをテストしてみましょう。

両方の実装をテストする

両方のケースで簡単なテストを作成しました。

@Test
public void testJavaImplementation() {
    char[] chars = new char[20000];
    new Example().reverseString(chars);
}

そして

@Test
fun testKotlinImplementation() {
    val chars = CharArray(20000)
    Example().reverseString(chars)
}

以下のためにJAVAのためにしながら、テストは問題なく成功しKotlinそれは無残に失敗したためStackOverflowError。ただし、JAVAメソッドに追加Intrinsics.checkParameterIsNotNull(s, "s")した後も失敗しました。

public void helper(char[] s, int left, int right) {
    Intrinsics.checkParameterIsNotNull(s, "s"); // add the same call here

    if (left >= right) return;
    char tmp = s[left];
    s[left] = s[right];
    s[right] = tmp;
    helper(s, left + 1, right - 1);
}

結論

あなたのKotlinのそれは呼び出しの方法は、より小さな再帰の深さを持っているIntrinsics.checkParameterIsNotNull(s, "s")すべてのステップで、したがって、そのより重いJAVAの対応。この自動生成されたメソッドが必要ない場合は、ここで回答しように、コンパイル中にnullチェックを無効にすることができます

ただし、利点tailrecがもたらすものを理解しているため(再帰呼び出しを反復呼び出しに変換する)、その呼び出しを使用する必要があります。


@ user207421すべてのメソッド呼び出しには、を含む独自のスタックフレームがありIntrinsics.checkParameterIsNotNull(...)ます。明らかに、このような各スタックフレームは、(のために一定量のメモリを必要LocalVariableTableとオペランドにそうスタックと)..
Anatolii

0

Kotlinは、スタックが少し空腹です(Int object params io int params)。ここに当てはまるtailrecソリューションの他に、xoringすることでローカル変数を排除できますtemp

fun helper(i: Int, j: Int, s: CharArray) {
    if (i >= j) {
        return
    }               // i: a          j: b
    s[j] ^= s[i]    //               j: a^b
    s[i] ^= s[j]    // i: a^a^b == b
    s[j] ^= s[i]    //               j: a^b^b == a
    helper(i + 1, j - 1, s)
}

これがローカル変数を削除するために機能するかどうかは完全にはわかりません。

また、jを削除すると次のようになります。

fun reverseString(s: CharArray): Unit {
    helper(0, s)
}

fun helper(i: Int, s: CharArray) {
    if (i >= s.lastIndex - i) {
        return
    }
    val t = s[s.lastIndex - i]
    s[s.lastIndex - i] = s[i]
    s[i] = t
    helper(i + 1, s)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.