Fiz essa pergunta para saber como aumentar o tamanho da pilha de chamadas em tempo de execução na JVM. Eu tenho uma resposta para isso e também tenho muitas respostas e comentários úteis relevantes sobre como o Java lida com a situação em que uma grande pilha de tempo de execução é necessária. Estendi minha pergunta com o resumo das respostas.
Originalmente, eu queria aumentar o tamanho da pilha da JVM para que programas como rodem sem a StackOverflowError
.
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
A configuração correspondente é o java -Xss...
sinalizador da linha de comando com um valor grande o suficiente. Para o programa TT
acima, funciona assim com a JVM do OpenJDK:
$ javac TT.java
$ java -Xss4m TT
Uma das respostas também apontou que os -X...
sinalizadores dependem da implementação. Eu estava usando
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
Também é possível especificar uma pilha grande apenas para um encadeamento (veja em uma das respostas como). Isso é recomendado java -Xss...
para evitar desperdiçar memória para threads que não precisam dela.
Fiquei curioso sobre o tamanho exato de uma pilha que o programa acima precisa, então eu a executei n
aumentada:
- -Xss4m pode ser suficiente para
fact(1 << 15)
- -Xss5m pode ser suficiente para
fact(1 << 17)
- -Xss7m pode ser suficiente para
fact(1 << 18)
- -Xss9m pode ser suficiente para
fact(1 << 19)
- -Xss18m pode ser suficiente para
fact(1 << 20)
- -Xss35m pode ser suficiente para
fact(1 << 21)
- -Xss68m pode ser suficiente para
fact(1 << 22)
- -Xss129m pode ser suficiente para
fact(1 << 23)
- -Xss258m pode ser suficiente para
fact(1 << 24)
- -Xss515m pode ser suficiente para
fact(1 << 25)
A partir dos números acima, parece que o Java está usando cerca de 16 bytes por quadro de pilha para a função acima, o que é razoável.
A enumeração acima contém pode ser suficiente em vez de é suficiente , porque o requisito da pilha não é determinístico: executá-lo várias vezes com o mesmo arquivo de origem e o mesmo -Xss...
às vezes obtém êxito e às vezes gera a StackOverflowError
. Por exemplo, para 1 << 20, -Xss18m
foi suficiente em 7 corridas de 10 e -Xss19m
nem sempre foi suficiente, mas -Xss20m
foi suficiente (em todas as 100 corridas de 100). A coleta de lixo, a entrada em funcionamento do JIT ou algo mais causa esse comportamento não determinístico?
O rastreamento de pilha impresso em StackOverflowError
(e possivelmente em outras exceções também) mostra apenas os 1024 elementos mais recentes da pilha de tempo de execução. Uma resposta abaixo demonstra como contar a profundidade exata atingida (que pode ser muito maior que 1024).
Muitas pessoas que responderam apontaram que é uma prática de codificação boa e segura considerar implementações alternativas, menos famintas de pilha, do mesmo algoritmo. Em geral, é possível converter um conjunto de funções recursivas em funções iterativas (usando um Stack
objeto eg , que é preenchido na pilha em vez de na pilha de tempo de execução). Para esta fact
função específica , é bastante fácil convertê-la. Minha versão iterativa seria semelhante a:
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
Para sua informação, como mostra a solução iterativa acima, a fact
função não pode computar o fatorial exato dos números acima de 65 (na verdade, mesmo acima de 20), porque o tipo interno do Java long
estouraria. A refatoração fact
para retornar um em BigInteger
vez de long
produziria resultados exatos para entradas grandes também.
fonte
Respostas:
Hmm ... funciona para mim e com muito menos que 999 MB de pilha:
(Windows JDK 7, compilação VM cliente 17.0-b05 e Linux JDK 6 - informações da mesma versão que você postou)
fonte
Suponho que você calculou a "profundidade de 1024" pelas linhas recorrentes no rastreamento de pilha?
Obviamente, o comprimento da matriz de rastreamento de pilha no Throwable parece estar limitado a 1024. Tente o seguinte programa:
fonte
Se você deseja jogar com o tamanho da pilha de encadeamentos, consulte a opção -Xss na JVM do Hotspot. Pode ser algo diferente em VMs sem Hotspot, pois os parâmetros -X para a JVM são específicos da distribuição, IIRC.
No Hotspot, parece
java -Xss16M
que você deseja fazer o tamanho 16 megas.Digite
java -X -help
se você deseja ver todos os parâmetros JVM específicos da distribuição que você pode transmitir. Não tenho certeza se isso funciona da mesma maneira em outras JVMs, mas ele imprime todos os parâmetros específicos do Hotspot.Pelo que vale a pena - eu recomendaria limitar o uso de métodos recursivos em Java. Não é muito bom para otimizá-los - para um, a JVM não suporta recursão de cauda (consulte A JVM evita otimizações de chamada de cauda? ). Tente refatorar seu código fatorial acima para usar um loop while em vez de chamadas de método recursivas.
fonte
A única maneira de controlar o tamanho da pilha no processo é iniciar um novo
Thread
. Mas você também pode controlar criando um processo sub Java de chamada automática com o-Xss
parâmetrofonte
java -Xss...
.Adicione esta opção
ao seu comando de envio de spark corrigirá esse problema.
fonte
É difícil dar uma solução sensata, pois você deseja evitar todas as abordagens sãs. Refatorar uma linha de código é a solução senível.
Nota: O uso de -Xss define o tamanho da pilha de cada encadeamento e é uma péssima idéia.
Outra abordagem é a manipulação de código de bytes para alterar o código da seguinte maneira;
dada todas as respostas para n> 127 é 0. Isso evita a alteração do código fonte.
fonte
fact
função na pergunta pode ser refatorada para usar muito menos espaço na pilha.Esquisito! Você está dizendo que deseja gerar uma recursão de 1 << 15 de profundidade ??? !!!!
Eu sugiro que NÃO tente. O tamanho da pilha será
2^15 * sizeof(stack-frame)
. Não sei qual é o tamanho do quadro de pilha, mas 2 ^ 15 é 32.768. Praticamente ... Bem, se parar em 1024 (2 ^ 10), você terá que aumentá-lo 2 ^ 5 vezes, 32 vezes maior do que na configuração atual.fonte
Outros pôsteres indicaram como aumentar a memória e que você pode memorizar as chamadas. Eu sugiro que, para muitas aplicações, você possa usar a fórmula de Stirling para aproximar n grande! muito rapidamente, sem quase nenhuma pegada de memória.
Dê uma olhada neste post, que tem algumas análises da função e do código:
http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/
fonte
Eu fiz um excersize de Anagram , que é como o problema de mudança de contagem, mas com 50 000 denominações (moedas). Não tenho certeza de que isso possa ser feito iterativamente , não me importo. Eu só sei que a opção -xss não teve efeito - sempre falhava após 1024 quadros de pilha (pode ser que o scala faça um trabalho ruim ao entregar para java ou a limitação printStackTrace. Não sei). Esta é uma opção ruim, como explicado de qualquer maneira. Você não deseja que todos os threads no aplicativo sejam monstruosos. No entanto, fiz alguns experimentos com o novo Thread (tamanho da pilha). Isso funciona de fato,
Você vê que a pilha pode crescer exponencialmente mais profunda com exponencialmente mais pilha alocada ao encadeamento.
fonte