Sou um iniciante em linguagem assembly e percebi que o código x86 emitido por compiladores geralmente mantém o ponteiro do frame por perto, mesmo no modo de liberação / otimizado, quando poderia usar o EBP
registro para outra coisa.
Eu entendo por que o ponteiro do frame pode tornar o código mais fácil de depurar e pode ser necessário se alloca()
for chamado dentro de uma função. No entanto, o x86 tem poucos registradores e usar dois deles para manter a localização do quadro de pilha quando um seria suficiente simplesmente não faz sentido para mim. Por que omitir o ponteiro do frame é considerado uma má ideia, mesmo em compilações otimizadas / liberadas?
performance
assembly
x86
dsimcha
fonte
fonte
alloca
) 3. facilidade de implementação de tempo de execução: exceto manipulação, sandbox, GCRespostas:
O ponteiro de quadro é um ponteiro de referência que permite a um depurador saber onde está a variável local ou um argumento com um único deslocamento constante. Embora o valor do ESP mude ao longo da execução, o EBP permanece o mesmo, tornando possível alcançar a mesma variável no mesmo deslocamento (como o primeiro parâmetro sempre estará em EBP + 8 enquanto os deslocamentos do ESP podem mudar significativamente, já que você estará pressionando / estourando coisas)
Por que os compiladores não descartam o ponteiro do frame? Porque com o ponteiro de quadro, o depurador pode descobrir onde as variáveis locais e os argumentos estão usando a tabela de símbolos, uma vez que eles têm a garantia de estar em um deslocamento constante para EBP. Caso contrário, não há uma maneira fácil de descobrir onde uma variável local está em qualquer ponto do código.
Como Greg mencionou, ele também ajuda a desenrolar a pilha para um depurador, já que o EBP fornece uma lista vinculada reversa de quadros de pilha, portanto, permitindo que o depurador descubra o tamanho do quadro de pilha (variáveis locais + argumentos) da função.
A maioria dos compiladores fornece uma opção para omitir ponteiros de quadro, embora isso torne a depuração muito difícil. Essa opção nunca deve ser usada globalmente, mesmo em código de lançamento. Você não sabe quando precisará depurar a falha de um usuário.
fonte
-fomit-frame-pointer
. Essa configuração é o padrão no gcc recente..eh_frame_hdr
seção também é usada para exceções de tempo de execução. Você o encontrará (comobjdump -h
) na maioria dos binários em um sistema Linux, é cerca de 16k para/bin/bash
, vs. 572B para GNU/bin/true
, 108k paraffmpeg
. Há uma opção do gcc para desativar a geração, mas é uma seção de dados "normal", não uma seção de depuração questrip
remove por padrão. Caso contrário, você não poderia retroceder por meio de uma função de biblioteca que não tivesse símbolos de depuração. Essa seção pode ser maior do que aspush/mov/pop
instruções que substitui, mas tem custo de tempo de execução quase zero (por exemplo, cache uop).Apenas adicionando meus dois centavos a respostas já boas.
É parte de uma boa arquitetura de linguagem ter uma cadeia de frames de pilha. O BP aponta para o quadro atual, onde as variáveis locais da sub-rotina são armazenadas. (Os locais estão em deslocamentos negativos e os argumentos estão em deslocamentos positivos.)
A ideia de que está impedindo que um registro perfeitamente bom seja usado na otimização levanta a questão: quando e onde a otimização realmente vale a pena?
A otimização só vale a pena em loops estreitos que 1) não chamam funções, 2) onde o contador do programa gasta uma fração significativa de seu tempo e 3) no código que o compilador realmente verá (ou seja, funções não pertencentes à biblioteca). Geralmente, essa é uma fração muito pequena do código geral, especialmente em sistemas grandes.
Outro código pode ser torcido e comprimido para se livrar dos ciclos, e isso simplesmente não importa, porque o contador do programa praticamente nunca está lá.
Eu sei que você não perguntou isso, mas na minha experiência, 99% dos problemas de desempenho não têm nada a ver com a otimização do compilador. Eles têm tudo a ver com design exagerado.
fonte
Depende do compilador, certamente. Já vi código otimizado emitido por compiladores x86 que usa livremente o registro EBP como um registro de propósito geral. (Não me lembro com qual compilador notei isso, no entanto.)
Os compiladores também podem optar por manter o registro EBP para auxiliar no desenrolar da pilha durante o tratamento de exceções, mas novamente isso depende da implementação precisa do compilador.
fonte
-fomit-frame-pointer
assume quando a otimização está habilitada. (quando a ABI permite). GCC, clang, ICC e MSVC fazem isso, IIRC, mesmo quando direcionados ao Windows de 32 bits. Sim, minha resposta em Por que é melhor usar o ebp do que o registrador esp para localizar parâmetros na pilha? mostra que mesmo o Windows de 32 bits pode omitir o ponteiro do quadro. O Linux x86 de 32 bits definitivamente pode e faz. E, claro, os ABIs de 64 bits permitiram a omissão do ponteiro do quadro desde o início.Isso é verdade apenas no sentido de que os opcodes podem endereçar apenas 8 registradores. O próprio processador terá, na verdade, muito mais registros do que isso e usará renomeação de registro, pipelining, execução especulativa e outros chavões do processador para contornar esse limite. A Wikipedia tem um bom parágrafo introdutório sobre o que um processador x86 pode fazer para superar o limite de registro: http://en.wikipedia.org/wiki/X86#Current_implementations .
fonte
O uso de stack frames tornou-se incrivelmente barato em qualquer hardware, mesmo remotamente moderno. Se você tiver frames de pilha baratos, salvar alguns registros não é tão importante. Tenho certeza de que frames de pilha rápidos versus mais registradores foi uma troca de engenharia e os frames de pilha rápidos venceram.
Quanto você está economizando com o registro puro? Vale a pena?
fonte