Parece-me que funcionaria perfeitamente bem na otimização da recursão de cauda em C e C ++, mas durante a depuração, nunca pareço ver uma pilha de quadros que indica essa otimização. Isso é bom, porque a pilha me diz o quão profunda é a recursão. No entanto, a otimização também seria agradável.
Algum compilador C ++ faz essa otimização? Por quê? Por que não?
Como instruo o compilador a fazer isso?
- Para MSVC:
/O2
ou/Ox
- Para o GCC:
-O2
ou-O3
Que tal verificar se o compilador fez isso em um determinado caso?
- Para MSVC, habilite a saída PDB para rastrear o código e, em seguida, inspecione o código
- Para o GCC ..?
Eu ainda aceitaria sugestões de como determinar se uma determinada função é otimizada assim pelo compilador (embora eu ache reconfortante que o Konrad me diga para assumi-la)
Sempre é possível verificar se o compilador faz isso fazendo uma recursão infinita e verificando se isso resulta em um loop infinito ou em um estouro de pilha (fiz isso com o GCC e descobri que -O2
é suficiente), mas quero ser capaz de verificar uma determinada função que eu sei que terminará de qualquer maneira. Eu adoraria ter uma maneira fácil de verificar isso :)
Após alguns testes, descobri que os destruidores arruinam a possibilidade de fazer essa otimização. Às vezes, pode valer a pena alterar o escopo de determinadas variáveis e temporários para garantir que elas fiquem fora do escopo antes do início da instrução de retorno.
Se qualquer destruidor precisar ser executado após a chamada final, a otimização da chamada final não poderá ser realizada.
fonte
gcc
possui uma opção mais estreita-foptimize-sibling-calls
para "otimizar chamadas de irmãos e chamadas recursivas". Esta opção (de acordo comgcc(1)
páginas de manual para versões 4.4, 4.7 e 4.8 alvejando várias plataformas) é habilitado em níveis-O2
,-O3
,-Os
.O gcc 4.3.2 destaca completamente esta função (
atoi()
implementação ruim / trivial )main()
. O nível de otimização é-O1
. Percebo que, se eu brincar com ele (mesmo mudando destatic
paraextern
, a recursão da cauda desaparece muito rápido, então eu não dependeria disso para a correção do programa.fonte
extern
método pode ser incorporado então.-O1
não há otimização inlining e sem recursão de cauda . Você tem que usar-O2
para isso (bem, no 4.2.x, que é bastante antigo agora, ainda não será incorporado). BTW Também vale acrescentar que o gcc pode otimizar a recursão mesmo quando não é estritamente uma cauda (como fatorial sem acumulador).Assim como o óbvio (os compiladores não fazem esse tipo de otimização, a menos que você solicite), há uma complexidade sobre a otimização de chamada de cauda em C ++: destruidores.
Dado algo como:
O compilador não pode (em geral) a chamada de cauda otimizar isso porque precisa chamar o destruidor
cls
após o retorno da chamada recursiva.Às vezes, o compilador pode ver que o destruidor não tem efeitos colaterais visíveis externamente (portanto, isso pode ser feito mais cedo), mas geralmente não pode.
Uma forma particularmente comum disso é onde
Funky
é realmente umstd::vector
ou similar.fonte
A maioria dos compiladores não faz nenhum tipo de otimização em uma compilação de depuração.
Se estiver usando o VC, tente uma versão compilada com as informações do PDB ativadas - isso permitirá que você rastreie o aplicativo otimizado e, esperançosamente, deverá ver o que deseja. Observe, no entanto, que a depuração e o rastreamento de uma compilação otimizada o guiarão por todo o lado, e muitas vezes você não poderá inspecionar as variáveis diretamente, pois elas acabam nos registros ou são totalmente otimizadas. É uma experiência "interessante" ...
fonte
Como Greg menciona, os compiladores não farão isso no modo de depuração. Não há problema em compilações de depuração serem mais lentas do que uma compilação de prod, mas elas não devem travar com mais frequência: e se você depende de uma otimização de chamada de cauda, elas podem fazer exatamente isso. Por esse motivo, geralmente é melhor reescrever a chamada final como um loop normal. :-(
fonte