재귀 메소드 호출로 인해 kotlin에서 StackOverFlowError가 발생하지만 Java에서는 그렇지 않습니다.


14

Java와 kotlin에서 거의 동일한 코드가 두 개 있습니다.

자바:

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추가하지 않은 경우 tailrec키워드를 추가하지 않습니다 helper.

왜이 함수가 Java 및 kolin에서도 작동 tailrec하지만 kotlin에서는 작동하지 않는지 알고 싶습니다 tailrec.

PS : 나는 무엇을 알고 tailrec


1
이것들을 테스트했을 때, Java 버전은 배열 크기가 최대 29500까지 작동하지만 Kotlin 버전은 18500 부근에서 멈출 것입니다. 그것은 큰 차이는 아니지만 큰 차이는 아닙니다. 큰 배열에서 작동하기 위해 이것이 필요한 경우 유일한 해결책은을 사용 tailrec하거나 재귀를 피하는 것입니다. 사용 가능한 스택 크기는 실행, JVM 및 설정 사이, 방법 및 해당 매개 변수에 따라 다릅니다. 그러나 순수한 호기심을 요구한다면 (완벽히 좋은 이유입니다!) 확실하지 않습니다. 아마도 바이트 코드를 봐야 할 것입니다.
gidds

답변:


7

나는이 기능이 작동 이유를 알고 싶어 자바 도에서 코 틀린tailrec있지만에서 코 틀린 없이 tailrec?

짧은 답변은 Kotlin 방법이 JAVA 방법보다 "무겁기"때문 입니다. 모든 호출에서 "provokes"라는 다른 메소드를 호출합니다 StackOverflowError. 아래에서 더 자세한 설명을 참조하십시오.

에 대한 Java 바이트 코드 reverseString()

KotlinJAVA 의 메소드에 대한 바이트 코드를 그에 따라 확인했습니다.

자바의 코 틀린 메소드 바이트 코드

...
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);
    }
}
...

자바의 자바 메소드 바이트 코드

...
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);
    }
}
...

따라서 두 가지 주요 차이점이 있습니다.

  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 에 대한 동안 테스트가 문제없이 성공 코 틀린 이 때문에에 비참하게 실패 StackOverflowError. 내가 추가 한 후 그러나 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);
}

결론

당신의 코 틀린의 이 호출로 방법은 작은 재귀 깊이가 Intrinsics.checkParameterIsNotNull(s, "s")그보다 무거운 따라서 모든 단계에서와 자바 대응. 이 자동 생성 방법을 원하지 않으면 여기에 응답 된대로 컴파일하는 동안 null 검사를 비활성화 할 수 있습니다

그러나 어떤 이점 tailrec이 재귀 호출을 반복 호출로 변환 하는지 이해 하므로 해당 기능을 사용해야합니다.


@ user207421 모든 메소드 호출에는를 포함하는 자체 스택 프레임이 Intrinsics.checkParameterIsNotNull(...)있습니다. 분명히, 이러한 각 스택 프레임에는 특정 양의 메모리가 필요합니다 ( LocalVariableTable및 피연산자 스택 등).
Anatolii

0

Kotlin은 스택 배고픈 것입니다 (Int object params io int params). 여기에 적합한 tailrec 솔루션 외에도 tempxor-ing을 통해 지역 변수 를 제거 할 수 있습니다 .

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.