Реализует ли 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) }
}
2 ответов
лучший способ проверить-это проверить ассемблерный код, генерируемый компилятором. Я взял код выше и скомпилировал его с помощью:
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 , %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 , %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 делает оптимизацию хвостового вызова в и случаях.
кроме того, isOdd
/isEven
функции интересны тем, что компилятор не только, кажется, выполняет TCO, но и inlines другую функцию в каждом случае.
да, компилятор swift выполняет оптимизацию хвостового вызова в некоторых случаях:
func sum(n: Int, acc: Int) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc: acc + 1) }
}
в качестве глобальной функции это будет использовать постоянное пространство стека на" самом быстром " уровне оптимизации (-O
).
если он находится внутри структуры все равно будет постоянного места в стеке. Однако в классе компилятор не выполняет tco, поскольку метод может быть переопределен во время выполнения.
Clang также поддерживает tco для Objective-C, но часто Arc calls release
после рекурсивный вызов, предотвращающий эту оптимизацию, см. эта статья Джонатана Ма для получения более подробной информации.
ARC также, похоже, предотвращает TCO в Swift:
func sum(n: Int, acc: Int, s: String?) -> Int {
if n == 0 { return acc }
else { return sum(n - 1, acc + 1, s) }
}
в моих тестах не было выполнено TCO.