Parece-me que tudo o que pode ser feito com uma pilha pode ser feito com a pilha, mas nem tudo que pode ser feito com a pilha pode ser feito com a pilha. Isso está correto? Então, por uma questão de simplicidade, e mesmo se perdermos uma pequena quantidade de desempenho com determinadas cargas de trabalho, não seria melhor seguir apenas um padrão (ou seja, a pilha)?
Pense na troca entre modularidade e desempenho. Sei que essa não é a melhor maneira de descrever esse cenário, mas, em geral, parece que a simplicidade de entendimento e design pode ser uma opção melhor, mesmo que exista um potencial para um melhor desempenho.
Respostas:
As pilhas são ruins na rápida alocação e desalocação de memória. Se você deseja capturar muitas pequenas quantidades de memória por um período limitado, um monte não é sua melhor escolha. Uma pilha, com seu algoritmo super-simples de alocação / desalocação, se destaca naturalmente (ainda mais se for incorporada ao hardware), e é por isso que as pessoas a usam para coisas como passar argumentos para funções e armazenar variáveis locais - o mais A desvantagem importante é que ele possui espaço limitado e, portanto, manter objetos grandes nele, ou tentar usá-lo para objetos de vida longa, são idéias ruins.
Livrar-se completamente da pilha para simplificar uma linguagem de programação é o caminho errado da IMO - uma abordagem melhor seria abstrair as diferenças, deixar o compilador descobrir que tipo de armazenamento usar, enquanto o programador reúne construções de nível mais próximas da maneira como os humanos pensam - e, de fato, linguagens de alto nível como C #, Java, Python etc. fazem exatamente isso. Eles oferecem sintaxe quase idêntica para objetos alocados em heap e primitivas alocadas em pilha ('tipos de referência' vs. 'tipos de valor' no lingo .NET), totalmente transparentes ou com algumas diferenças funcionais que você deve entender para usar a linguagem corretamente (mas você realmente não precisa saber como uma pilha e uma pilha funcionam internamente).
fonte
Simplificando, uma pilha não é um pouco de desempenho. É centenas ou milhares de vezes mais rápido que o monte. Além disso, a maioria das máquinas modernas possui suporte de hardware para a pilha (como x86) e essa funcionalidade de hardware, por exemplo, a pilha de chamadas não pode ser removida.
fonte
Não
A área de pilha em C ++ é incrivelmente rápida em comparação. Acho que nenhum desenvolvedor experiente de C ++ estaria aberto a desativar essa funcionalidade.
Com o C ++, você tem escolha e controle. Os designers não estavam particularmente inclinados a introduzir recursos que adicionavam tempo ou espaço de execução significativo.
Exercitando essa escolha
Se você deseja criar uma biblioteca ou programa que exija que cada objeto seja alocado dinamicamente, você pode fazer isso com C ++. Seria executado de forma relativamente lenta, mas você poderia ter essa 'modularidade'. Para o resto de nós, a modularidade é sempre opcional, introduza-a conforme necessário, pois ambas são necessárias para implementações boas / rápidas.
Alternativas
Existem outros idiomas que exigem que o armazenamento para cada objeto seja criado no heap; é bastante lento, comprometendo os designs (programas do mundo real) de uma maneira que é pior do que ter que aprender os dois (IMO).
Ambos são importantes, e o C ++ fornece o uso eficiente de energia para cada cenário. Dito isto, a linguagem C ++ pode não ser ideal para o seu design, se esses fatores no seu OP forem importantes para você (por exemplo, leia em linguagens de nível superior).
fonte
Na verdade, o impacto no desempenho provavelmente será considerável!
Como outros já apontaram, as pilhas são uma estrutura extremamente eficiente para gerenciar dados que obedecem às regras LIFO (último a entrar, primeiro a sair). A alocação / liberação de memória na pilha geralmente é apenas uma alteração em um registro na CPU. Alterar um registro é quase sempre uma das operações mais rápidas que um processador pode executar.
A pilha geralmente é uma estrutura de dados bastante complexa e a alocação / liberação de memória exigirá muitas instruções para fazer toda a contabilidade associada. Pior ainda, em implementações comuns, toda chamada para trabalhar com o heap tem o potencial de resultar em uma chamada para o sistema operacional. As chamadas do sistema operacional consomem muito tempo! O programa normalmente precisa alternar do modo de usuário para o modo de kernel, e sempre que isso acontece, o sistema operacional pode decidir que outros programas têm necessidades mais prementes e que seu programa precisará aguardar.
fonte
Simula usou a pilha para tudo. Colocar tudo no heap sempre induz mais um nível de indireção para as variáveis locais e coloca pressão adicional no Garbage Collector (você deve levar em conta que o Garbage Collectors realmente foi péssimo naquela época). É em parte por isso que Bjarne inventou o C ++.
fonte
As pilhas são extremamente eficientes para dados LIFO, como os metadados associados às chamadas de função, por exemplo. A pilha também aproveita os recursos inerentes de design da CPU. Como o desempenho nesse nível é fundamental para praticamente tudo o mais em um processo, aceitar esse hit "pequeno" nesse nível se propagará muito amplamente. Além disso, a memória heap é móvel pelo sistema operacional, o que seria mortal para as pilhas. Embora uma pilha possa ser implementada na pilha, ela exige sobrecarga que afetará literalmente todas as partes de um processo no nível mais granular.
fonte
"eficiente" em termos de você escrever código, talvez, mas certamente não em termos de eficiência do software. As alocações de pilha são essencialmente livres (são necessárias apenas algumas instruções da máquina para mover o ponteiro da pilha e reservar espaço na pilha para variáveis locais).
Como a alocação de pilha quase não leva tempo, uma alocação mesmo em um heap muito eficiente será 100k (se não 1M +) vezes mais lenta.
Agora imagine quantas variáveis locais e outras estruturas de dados um aplicativo típico usa. Cada pequeno "i" que você usa como contador de loop é alocado um milhão de vezes mais devagar.
Certifique-se de que o hardware seja rápido o suficiente, você pode escrever um aplicativo que use apenas heap. Agora, imagine agora que tipo de aplicativo você poderia escrever se aproveitasse o heap e usasse o mesmo hardware.
fonte
Você pode ter interesse em "A coleta de lixo é rápida, mas a pilha é mais rápida".
http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z
Se eu li corretamente, esses caras modificaram um compilador C para alocar "quadros de pilha" na pilha e, em seguida, usam a coleta de lixo para desalocar os quadros em vez de estourar a pilha.
Os "quadros de pilha" alocados à pilha superam decisivamente os "quadros de pilha" alocados na pilha.
fonte
Como a pilha de chamadas funciona em um heap? Essencialmente, você teria que alocar uma pilha na pilha em todos os programas; então, por que não fazer com que o hardware do OS + faça isso por você?
Se você quiser que as coisas sejam realmente simples e eficientes, dê ao usuário um pedaço de memória e deixe-o lidar com isso. É claro que ninguém quer implementar tudo sozinho e é por isso que temos uma pilha e uma pilha.
fonte
A pilha e a pilha são necessárias. Eles são usados em diferentes situações, por exemplo:
Basicamente, os mecanismos não podem ser comparados, porque muitos detalhes são diferentes. A única coisa comum a eles é que ambos lidam com a memória de alguma forma.
fonte
Os computadores modernos têm várias camadas de memória cache, além de um sistema de memória principal grande, mas lento. Pode-se fazer dezenas de acessos à memória cache mais rápida no tempo necessário para ler ou gravar um byte do sistema de memória principal. Portanto, acessar um local mil vezes é muito mais rápido do que acessar 1.000 (ou mesmo 100) locais independentes uma vez cada. Como a maioria dos aplicativos aloca e desaloca repetidamente pequenas quantidades de memória próximo ao topo da pilha, os locais na parte superior da pilha são usados e reutilizados uma quantidade enorme, de modo que a grande maioria (99% + em um aplicativo típico) de acessos de pilha podem ser tratados usando memória cache.
Por outro lado, se um aplicativo criar e abandonar repetidamente objetos de heap para armazenar informações de continuação, todas as versões de todos os objetos de pilha que já foram criados teriam que ser gravadas na memória principal. Mesmo que a grande maioria desses objetos fosse completamente inútil quando a CPU quisesse reciclar as páginas de cache em que eles começaram, a CPU não teria como saber disso. Conseqüentemente, a CPU precisaria perder muito tempo executando gravações lentas de informações inúteis na memória. Não é exatamente uma receita de velocidade.
Outra coisa a considerar é que, em muitos casos, é útil saber que uma referência de objeto passada para uma rotina não será usada quando a rotina terminar. Se parâmetros e variáveis locais são passados através da pilha e se a inspeção do código da rotina revelar que ela não persiste em uma cópia da referência passada, o código que chama a rotina pode ter certeza de que, se nenhuma referência externa ao objeto existia antes da chamada, nenhum existirá depois. Por outro lado, se parâmetros foram passados por meio de objetos heap, conceitos como "após o retorno de uma rotina" se tornam um pouco mais nebulosos, pois se o código mantivesse uma cópia da continuação, seria possível que a rotina "retornasse" mais de uma vez após um chamada única.
fonte