Insbesondere wenn ich folgenden Code habe:
func sum(n: Int, acc: Int) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc + n) }
}
Wird der Swift-Compiler es zu einer Schleife optimieren? Und in einem interessanteren Fall weiter unten?
func isOdd(n: Int) -> Bool {
if n == 0 { return false; }
else { return isEven(n - 1) }
}
func isEven(n: Int) -> Bool {
if n == 0 { return true }
else { return isOdd(n - 1) }
}
swift
tail-call-optimization
Alfa07
quelle
quelle
for
Schleife ohne Testklausel ausführt, zfor (int i = 0; ; i++) { println("%d", i); }
.Antworten:
Am besten überprüfen Sie den vom Compiler generierten Assembler-Code. Ich habe den obigen Code genommen und ihn kompiliert mit:
swift -O3 -S tco.swift >tco.asm
Der relevante Teil der Ausgabe
.globl __TF3tco3sumFTSiSi_Si .align 4, 0x90 __TF3tco3sumFTSiSi_Si: pushq %rbp movq %rsp, %rbp testq %rdi, %rdi je LBB0_4 .align 4, 0x90 LBB0_1: movq %rdi, %rax decq %rax jo LBB0_5 addq %rdi, %rsi jo LBB0_5 testq %rax, %rax movq %rax, %rdi jne LBB0_1 LBB0_4: movq %rsi, %rax popq %rbp retq LBB0_5: ud2 .globl __TF3tco5isOddFSiSb .align 4, 0x90 __TF3tco5isOddFSiSb: pushq %rbp movq %rsp, %rbp testq %rdi, %rdi je LBB1_1 decq %rdi jo LBB1_9 movb $1, %al LBB1_5: testq %rdi, %rdi je LBB1_2 decq %rdi jo LBB1_9 testq %rdi, %rdi je LBB1_1 decq %rdi jno LBB1_5 LBB1_9: ud2 LBB1_1: xorl %eax, %eax LBB1_2: popq %rbp retq .globl __TF3tco6isEvenFSiSb .align 4, 0x90 __TF3tco6isEvenFSiSb: pushq %rbp movq %rsp, %rbp movb $1, %al LBB2_1: testq %rdi, %rdi je LBB2_5 decq %rdi jo LBB2_7 testq %rdi, %rdi je LBB2_4 decq %rdi jno LBB2_1 LBB2_7: ud2 LBB2_4: xorl %eax, %eax LBB2_5: popq %rbp retq
Der generierte Code enthält keine Aufrufanweisungen, nur bedingte Sprünge (
je
/jne
/jo
/jno
). Dies deutet eindeutig darauf hin, dass Swift in beiden Fällen Tail-Call-Optimierungen durchführt .Darüber hinaus sind die
isOdd
/isEven
-Funktionen insofern interessant, als der Compiler nicht nur TCO auszuführen scheint, sondern auch jeweils die andere Funktion inline.quelle
call
Anweisungen im generierten Code gibt, nur bedingte Sprünge (je
/jne
/jo
/jno
)Ja, der schnelle Compiler führt in einigen Fällen eine Tail-Call-Optimierung durch:
func sum(n: Int, acc: Int) -> Int { if n == 0 { return acc } else { return sum(n - 1, acc: acc + 1) } }
Als globale Funktion wird hierdurch ein konstanter Stapelspeicher auf der Optimierungsstufe "Schnellste" (
-O
) verwendet.Wenn es sich innerhalb einer Struktur befindet, wird immer noch konstanter Stapelspeicher verwendet. Innerhalb einer Klasse führt der Compiler jedoch kein tco aus, da die Methode zur Laufzeit möglicherweise überschrieben wird.
Clang unterstützt auch tco für Objective-C, aber häufig ARC-Aufrufe
release
nach dem rekursiven Aufruf, wodurch diese Optimierung verhindert wird (siehe diesen Artikel von Jonathon Mah) Weitere Informationen finden .ARC scheint auch TCO in Swift zu verhindern:
func sum(n: Int, acc: Int, s: String?) -> Int { if n == 0 { return acc } else { return sum(n - 1, acc + 1, s) } }
In meinen Tests wurden keine TCO durchgeführt.
quelle