Tengo dos códigos casi idénticos en java y kotlin
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);
}
Kotlin:
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)
}
El código de Java pasa la prueba con una gran entrada, pero el código de Kotlin causa una, a StackOverFlowError
menos que agregue una tailrec
palabra clave antes de la helper
función en Kotlin.
Quiero saber por qué esta función funciona en java y también en kolin con tailrec
pero sin kotlin sin tailrec
?
PD:
sé qué tailrec
hacer
tailrec
o evitar la recurrencia ; el tamaño de la pila disponible varía entre ejecuciones, entre JVM y configuraciones, y dependiendo del método y sus parámetros. Pero si lo preguntas por pura curiosidad (¡una razón perfectamente buena!), Entonces no estoy seguro. Probablemente necesite mirar el código de bytes.Respuestas:
La respuesta corta es porque su método Kotlin es "más pesado" que el de JAVA . En cada llamada se llama a otro método que "provoca"
StackOverflowError
. Entonces, vea una explicación más detallada a continuación.Java bytecode equivalentes para
reverseString()
Verifiqué el código de byte para sus métodos en Kotlin y JAVA correspondientemente:
Código de bytes del método Kotlin en JAVA
Código de bytes del método JAVA en JAVA
Entonces, hay 2 diferencias principales:
Intrinsics.checkParameterIsNotNull(s, "s")
se invoca para cada unohelper()
en la versión de Kotlin .Entonces, probemos cómo
Intrinsics.checkParameterIsNotNull(s, "s")
solo afecta el comportamiento.Probar ambas implementaciones
He creado una prueba simple para ambos casos:
Y
Para JAVA, la prueba tuvo éxito sin problemas, mientras que para Kotlin falló miserablemente debido a a
StackOverflowError
. Sin embargo, después de agregarIntrinsics.checkParameterIsNotNull(s, "s")
al método JAVA , también falló:Conclusión
Su método Kotlin tiene una profundidad de recursión menor, ya que invoca
Intrinsics.checkParameterIsNotNull(s, "s")
en cada paso y, por lo tanto, es más pesado que su contraparte JAVA . Si no desea este método generado automáticamente, puede deshabilitar las comprobaciones nulas durante la compilación como se responde aquíSin embargo, dado que comprende qué beneficio
tailrec
trae (convierte su llamada recursiva en una iterativa), debe usar esa.fuente
Intrinsics.checkParameterIsNotNull(...)
. Obviamente, cada uno de esos marcos de pila requiere una cierta cantidad de memoria (para laLocalVariableTable
pila de operandos, etc.)Kotlin es solo un poquito más hambriento (Int object params io int params). Además de la solución tailrec que se ajusta aquí, puede eliminar la variable local
temp
haciendo xor-ing:No estoy completamente seguro de si esto funciona para eliminar una variable local.
También eliminar j podría hacer:
fuente