Quantas existem chamadas de função aninhadas?


15

Citado no MSDN sobre StackOverflowException :

A exceção que é lançada quando a pilha de execução é estourada porque contém muitas chamadas de método aninhadas.

Too manyé bem vago aqui. Como sei quando muitos são realmente muitos? Milhares de chamadas de função? Milhões? Suponho que ele deva estar relacionado de alguma forma à quantidade de memória no computador, mas é possível chegar a uma ordem de magnitude aproximadamente precisa?

Estou preocupado com isso porque estou desenvolvendo um projeto que envolve um uso intenso de estruturas recursivas e chamadas de função recursivas. Não quero que o aplicativo falhe quando começar a usá-lo para mais do que apenas pequenos testes.


4
É relativamente fácil converter uma função recursiva em uma função não recursiva. Basta colocar seus dados em um Stack<T>.
22713 Brian

1
Qual o tamanho de "muitos" depende inteiramente de você definir. Para .NET, use editbin /stack:WHATEVER-NUMBER-YOU-LIKE yourexefile.exe.
SK-logic

Respostas:


28

Estou preocupado com isso porque estou desenvolvendo um projeto que envolve um uso intenso de estruturas recursivas e chamadas de função recursivas. Não quero que o aplicativo falhe quando começar a usá-lo para mais do que apenas pequenos testes.

A menos que seu ambiente de linguagem suporte a otimização de chamada final (e sua recursão seja uma chamada final), uma regra básica básica é: a profundidade da recursão deve ser garantida como O (log n), ou seja, usando algoritmos ou estruturas de dados baseadas em dividir e - conquistar (como árvores, a maioria dos alogoritmos de classificação, etc.) é bom, mas qualquer coisa linear (como implementações recursivas de manipulação de lista vinculada) não é.


3
+1. Resposta muito boa, eu tenho usado implicitamente esta regra, mas não a conhecia.
Giorgio

Hoje em dia, praticamente qualquer compilador com qualidade de produção digno da frase adjetiva oferecerá suporte à otimização da chamada de cauda.
John R. Strohm

1
@ JohnR.Strohm No entanto, o OP marcou a questão .NET, então o AFAIK apenas o jitter de 64 bits reduz a otimização de chamada no momento.
Mark Hurd

1
Só para não haver confusão, o x86 e o ​​x64 sempre honram a "cauda" do CIL. prefixo de instrução. Então, para resumir, no .NET, o F # completa a otimização da chamada de cauda, ​​o C # não e o jitter x64 o faz se for "fácil" (não se pode depender disso). Veja blogs.msdn.com/b/clrcodegeneration/archive/2009/05/11/…
Stephen Swensen

1
É verdade, mas em alguns casos, como em um infame IIS que hospeda o ASP.NET, mesmo uma mera profundidade de recursão de O (log n) pode falhar devido ao seu limite patético, injustificado e risível de minúscula pilha de profundidade, que processa praticamente qualquer recursão (ou mesmo uma longa cadeia de chamadas aninhadas não recursivas) é impossível. A única maneira de contornar é editar o próprio binário iis.
SK-logic

17

Por padrão, o CLR aloca 1 MB para a pilha para cada thread (consulte este artigo ). Portanto, são necessárias muitas chamadas para exceder esse valor. Isso varia dependendo de quanto espaço na pilha cada chamada está usando para itens como parâmetros e variáveis ​​locais.

Você pode até fazer StackOverflowExceptionuma ligação com uma única ligação se estiver disposto a ser um pouco ortodoxo:

private static unsafe void BlowUpTheStack()
{
    var x = stackalloc byte[1000000000];
    Console.WriteLine("Oh no, I've blown up the stack!");
}

Infelizmente, esse padrão razoável é frequentemente violado (por exemplo, no asp.net).
SK-logic

2

Como Cole Campbell notou o tamanho da memória e Michael Borgwardt notou a otimização da chamada de cauda , não os abordarei.

Outra coisa a ter em atenção é o CPS, que pode ser usado para otimizar várias funções entrelaçadas, onde a otimização da chamada de cauda é para funções únicas.

Você pode aumentar o tamanho da pilha como fizemos aqui e esteja ciente de que o código de 64 bits consome a pilha mais rapidamente que o código de 32 bits.

É importante notar que executamos um dos exemplos em F # interativo por mais de 40 horas sem explodir a pilha. Sim, foi uma chamada de função executada por si só continuamente até a conclusão bem-sucedida.

Além disso, se você precisar fazer uma cobertura de código para descobrir onde os problemas ocorrem e não tiver cobertura de código com o VS, você pode usar, TestDriven.NET

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.