web-dev-qa-db-ja.com

Swiftは末尾呼び出しの最適化を実装しますか?相互再帰の場合?

特に次のコードがある場合:

func sum(n: Int, acc: Int) -> Int {
  if n == 0 { return acc }
  else { return sum(n - 1, acc + n) }
}

Swiftコンパイラーはそれをループに最適化しますか?そして、以下のより興味深いケースでそうしますか?

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

チェックする最善の方法は、コンパイラーによって生成されたアセンブリー言語コードを調べることです。上記のコードを取得してコンパイルしました:

Swift -O3 -S tco.Swift >tco.asm

出力の関連部分

.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

生成されたコードには呼び出し命令はなく、条件付きジャンプ(je/jne/jo/jno)のみがあります。これは、Swiftがbothの場合に末尾呼び出しの最適化を行うことを明確に示唆しています。

さらに、isOdd/isEven関数は、コンパイラがTCOを実行するように見えるだけでなく、それぞれの場合に他の関数をインライン化するという点で興味深いです。

67
Ferruccio

はい、Swiftコンパイラは、場合によっては末尾呼び出しの最適化を実行します。

func sum(n: Int, acc: Int) -> Int {
    if n == 0 { return acc }
    else { return sum(n - 1, acc: acc + 1) }
}

グローバル関数として、これは「最速」最適化レベル(-O)。

構造体内にある場合、一定のスタックスペースを使用します。ただし、クラス内では、メソッドは実行時にオーバーライドされる可能性があるため、コンパイラはtcoを実行しません。

ClangはObjective-Cのtcoもサポートしますが、多くの場合、ARCは再帰呼び出しの後にreleaseを呼び出し、この最適化を防ぎます。詳細については この記事のJonathon Mah を参照してください。

ARCはSwiftのTCOを防ぐようにも思われます:

func sum(n: Int, acc: Int, s: String?) -> Int {
    if n == 0 { return acc }
    else { return sum(n - 1, acc + 1, s) }
}

私のテストではTCOは実行されませんでした。

21
Sebastian