En particulier si j'ai le code suivant:
func sum(n: Int, acc: Int) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc + n) }
}
Le compilateur Swift l'optimisera-t-il en boucle? Et le fait-il dans un cas plus intéressant ci-dessous?
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) }
}
La meilleure façon de vérifier est d'examiner le code du langage d'assembly généré par le compilateur. J'ai pris le code ci-dessus et l'ai compilé avec:
Swift -O3 -S tco.Swift >tco.asm
La partie pertinente de la sortie
.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
Il n'y a pas d'instructions d'appel dans le code généré, seulement des sauts conditionnels (je
/jne
/jo
/jno
). Cela suggère clairement que Swift fait des optimisations d'appel de queue dans les deux cas.
De plus, les fonctions isOdd
/isEven
sont intéressantes dans la mesure où le compilateur semble non seulement effectuer le TCO mais aussi inline l'autre fonction dans chaque cas.
Oui, le compilateur Swift effectue l'optimisation des appels de queue dans certains cas:
func sum(n: Int, acc: Int) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc: acc + 1) }
}
En tant que fonction globale, cela utilisera un espace de pile constant au niveau d'optimisation "le plus rapide" (-O
).
S'il se trouve à l'intérieur d'une structure, il utilisera toujours un espace de pile constant. Cependant, au sein d'une classe, le compilateur n'exécute pas tco car la méthode peut être remplacée au moment de l'exécution.
Clang prend également en charge tco pour Objective-C mais souvent ARC appelle release
après l'appel récursif, empêchant ainsi cette optimisation, voir cet article de Jonathon Mah pour plus de détails.
ARC semble également empêcher le TCO dans Swift:
func sum(n: Int, acc: Int, s: String?) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc + 1, s) }
}
Aucun TCO n'a été réalisé dans mes tests.