web-dev-qa-db-fra.com

Swift implémente-t-il l'optimisation des appels de queue? Et dans le cas de récurrence mutuelle?

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) }
}
64
Alfa07

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.

67
Ferruccio

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.

21
Sebastian