Swift是否实现了尾部呼叫优化? 并在相互recursion的情况下?
特别是如果我有以下代码:
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) } }
检查最好的方法是检查编译器生成的汇编语言代码。 我拿了上面的代码,并编译它:
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在这两种情况下都会做tail调用优化。
另外, isOdd
/ isEven
函数是有趣的,因为编译器似乎不仅执行TCO,而且还在每种情况下内联其他函数。
是的,swift编译器在某些情况下执行tail call优化:
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,但在recursion调用之后经常会release
ARC调用,从而阻止了这种优化,请参阅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) } }
在我的testing中没有执行TCO。