É realmente uma boa prática desabilitar otimizações durante as fases de desenvolvimento e depuração?


15

Eu li Programação de microcontroladores PIC de 16 bits em C , e há esta afirmação no livro:

Porém, durante as fases de desenvolvimento e depuração de um projeto, é sempre uma boa prática desativar todas as otimizações, pois elas podem modificar a estrutura do código que está sendo analisado e tornar problemático o posicionamento de etapa única e ponto de interrupção.

Confesso que fiquei um pouco confuso. Não entendi se o autor disse isso por causa do período de avaliação C30 ou se é realmente uma boa prática.

Gostaria de saber se você realmente usa essa prática e por quê?

Respostas:


16

Isso é bastante padrão na engenharia de software como um todo - quando você otimiza o código, o compilador pode reorganizar as coisas da maneira que desejar, desde que você não consiga diferenciar a operação. Portanto, por exemplo, se você inicializar uma variável dentro de cada iteração de um loop e nunca alterar a variável dentro do loop, o otimizador poderá mover essa inicialização para fora do loop, para que você não perca tempo com ela.

Também pode perceber que você calcula um número com o qual não faz nada antes de sobrescrever. Nesse caso, pode eliminar a computação inútil.

O problema com a otimização é que você deseja colocar um ponto de interrupção em algum trecho de código, que o otimizador moveu ou eliminou. Nesse caso, o depurador não pode fazer o que você deseja (geralmente, ele colocará o ponto de interrupção em algum lugar próximo). Portanto, para tornar o código gerado mais parecido com o que você escreveu, desative as otimizações durante a depuração - isso garante que o código que você deseja quebrar esteja realmente lá.

Você precisa ter cuidado com isso, no entanto, como dependendo do seu código, a otimização pode quebrar as coisas! Em geral, o código que é quebrado por um otimizador funcionando corretamente é na verdade apenas um código de buggy que está fugindo de algo, então você geralmente quer descobrir por que o otimizador o quebra.


5
O contraponto a esse argumento é que o otimizador provavelmente tornará as coisas menores e / ou mais rápidas, e se você tiver um código restrito de tempo ou tamanho, poderá quebrar algo desativando a otimização e perder seu tempo depurando um problema que não ' realmente existe. Obviamente, o depurador também pode tornar seu código mais lento e maior.
9138 Kevin Vermeer

Onde posso aprender mais sobre isso?
Daniel Grillo

Eu não trabalhei com o compilador C30, mas para o compilador C18 havia uma nota / manual de aplicativo para o compilador que cobria as otimizações suportadas.
Mark

@O Engenheiro: Verifique os documentos do seu compilador para saber quais otimizações ele suporta. A otimização varia muito, dependendo do compilador, bibliotecas e arquitetura de destino.
Michael Kohne

Novamente, não para o compilador C30, mas o gcc publica uma lista LONGA das várias otimizações que podem ser aplicadas. Você também pode usar esta lista para obter otimização refinada, caso tenha uma estrutura de controle específica que deseja manter intacta. A lista está aqui: gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Kevin Vermeer

7

Enviei esta pergunta a Jack Ganssle e foi isso que ele me respondeu:

Daniel,

Prefiro depurar usando as otimizações que estiverem no código liberado. A NASA diz "teste o que você voa, voe o que você testa". Em outras palavras, não faça testes e altere o código!

No entanto, às vezes é preciso desativar as otimizações para que o depurador funcione. Eu tento desativá-los apenas nos módulos em que estou trabalhando. Por esse motivo, acredito em manter os arquivos pequenos, digamos algumas centenas de linhas de código.

Tudo de bom, Jack


Eu acho que há uma distinção não declarada entre os dois parágrafos nesta resposta. O teste refere-se a um procedimento que deve demonstrar que os produtos macios, firmes e / ou duros funcionam corretamente. Depuração é o processo no qual o código é percorrido instrução por instrução para ver por que não está funcionando [ainda].
Kevin Vermeer

É bom ter uma escolha. Portanto, o teste pode abranger mais variedades / permutações com e sem otimização.

@reemrevnivek, quando você está depurando, não está testando também?
Daniel Grillo #

@O Engenheiro - Não. Só depuro se o teste falhar.
Kevin Vermeer

6

Depende, e isso geralmente é verdade para todas as ferramentas, não apenas para o C30.

As otimizações geralmente removem e / ou reestruturam o código de várias maneiras. A instrução switch pode ser reimplementada com uma construção if / else ou, em alguns casos, pode ser removida por completo. y = x * 16 pode ser substituído por uma série de mudanças à esquerda, etc., embora esse último tipo de otimização ainda possa ser feito, geralmente é a reestruturação da instrução de controle que o recebe.

Isso pode impossibilitar a execução de um depurador no código C porque as estruturas que você definiu em C não existem mais, elas foram substituídas ou reordenadas pelo compilador em algo que o compilador acredita que será mais rápido ou ocupará menos espaço. Também pode tornar impossível definir pontos de interrupção a partir da listagem C, pois as instruções que você interrompeu podem não existir mais. Por exemplo, você pode tentar definir um ponto de interrupção dentro de uma instrução if, mas o compilador pode ter removido esse if. Você pode tentar definir um ponto de interrupção dentro de um loop while ou for, mas o compilador decidiu desenrolar esse loop para que ele não exista mais.

Por esse motivo, se você pode depurar com otimizações desativadas, geralmente é mais fácil. Você deve sempre testar novamente com as otimizações ativadas. Essa é a única maneira de descobrir que você perdeu um importante volatilee está causando falhas intermitentes (ou alguma outra estranheza).

No caso de desenvolvimento incorporado, você deve ter cuidado com as otimizações de qualquer maneira. Especificamente em seções de código com tempo crítico, algumas interrupções, por exemplo. Nesses casos, você deve codificar os bits críticos no assembly ou usar diretivas do compilador para garantir que essas seções não sejam otimizadas, para que você saiba que eles têm um tempo de execução fixo ou um tempo de execução fixo no pior caso.

A outra pegadinha pode ser encaixar o código no uC. Você pode precisar de otimizações de densidade de código para simplesmente encaixar seu código no chip. Essa é uma das razões pelas quais geralmente é uma boa idéia começar com a maior capacidade de ROM uC de uma família e escolher apenas uma menor para fabricação, depois que seu código estiver bloqueado.


5

Geralmente, eu depurava com qualquer configuração que planejasse lançar. Se eu fosse liberar código otimizado, eu depuraria com código otimizado. Se eu fosse liberar código não otimizado, eu depuraria com código não otimizado. Eu Faço Isso Por Duas Razões. Primeiro, os otimizadores podem fazer diferenças de tempo significativas o suficiente para fazer com que o produto final se comporte de maneira diferente do código não otimizado. Segundo, embora a maioria seja muito boa, os fornecedores de compiladores cometem erros e o código otimizado pode produzir resultados diferentes do código não otimizado. Como resultado, eu gosto de obter o máximo de tempo possível com qualquer configuração que pretendo lançar.

Dito isto, os otimizadores podem dificultar a depuração, conforme observado nas respostas anteriores. Se eu encontrar uma seção específica do código que é difícil de depurar, desligarei temporariamente o otimizador, faça a depuração para obter o código funcionando, depois ligue novamente o otimizador e teste novamente.


1
Mas o passo único do código pode ser quase impossível com as otimizações ativadas. Depure com otimizações desativadas e execute seus testes de unidade com o código de liberação.
precisa

3

Minha estratégia normal é desenvolver com a otimização final (máximo para tamanho ou velocidade, conforme apropriado), mas desative temporariamente a otimização se precisar depurar para rastrear algo. Isso reduz o risco de erros surgirem como resultado da alteração dos níveis de otimização.

Um modo típico de falha é quando o aumento da otimização faz com que bugs anteriormente invisíveis apareçam devido ao fato de você não ter declarado variáveis ​​como voláteis quando necessário - isso é essencial para informar ao compilador que coisas não devem ser 'otimizadas'.


2

Use qualquer forma com a qual você libere, os depuradores e a compilação para depuração ocultam (MUITOS) erros que você não vê até compilar para a liberação. Até lá, é muito mais difícil encontrar esses bugs, vs depuração conforme você avança. 20 anos e meio agora e nunca tive uso de um gdb ou outro como depurador, sem necessidade de observar variáveis ​​ou passo único. Centenas a milhares de linhas por dia. Portanto, é possível, não ser levado a pensar o contrário.

A compilação para depuração e, posteriormente, a compilação para liberação podem e levarão de duas a mais do que o dobro do esforço. Se você entrar em uma ligação e precisar usar uma ferramenta como um depurador, compile para que o depurador funcione com o problema específico, em seguida, volte à operação normal.

Outros problemas também são verdadeiros, como o otimizador torna o código mais rápido, para que, em particular, o seu tempo mude com as opções do compilador e que possam afetar a funcionalidade do seu programa, use novamente a opção de compilação que pode ser entregue durante toda a fase. Compiladores também são programas e têm bugs e otimizadores cometem erros e alguns não têm fé nisso. Se for esse o caso, não há nada errado com a compilação sem otimização, faça-o dessa maneira o tempo todo. O caminho que eu prefiro é compilar para otimização; se eu suspeitar de um problema do compilador, desabilite a otimização, se isso for corrigido, geralmente vai e volta às vezes, examinando a saída do assembler para descobrir o motivo.


1
+1 apenas para elaborar sua boa resposta: geralmente a compilação no modo "depuração" irá amontoar a pilha / pilha em torno de variáveis ​​com espaço não alocado para mitigar pequenos erros de write-off-the-end e formato de cadeia de caracteres. Você obterá com mais freqüência uma boa falha de tempo de execução se compilar na versão.
Morten Jensen

2

Eu sempre desenvolvo código com -O0 (opção gcc para desativar a otimização). Quando sentir que estou no ponto em que quero começar a deixar as coisas avançarem mais em direção a um lançamento, começarei com -Os (otimizar o tamanho), pois geralmente quanto mais código você puder manter no cache, melhor será, mesmo que não seja otimizado para super-duper.

Acho que o gdb funciona muito melhor com o código -O0, e é muito mais fácil seguir se você precisar entrar no assembly. Alternar entre -O0 e -Os também permite ver o que o compilador está fazendo com seu código. Às vezes, é uma educação bastante interessante e também pode descobrir erros do compilador ... aquelas coisas desagradáveis ​​que fazem você arrancar o cabelo tentando descobrir o que há de errado com o seu código!

Se eu realmente precisar, começarei a adicionar -fdata-section e -fcode-section com --gc-seções, que permitem ao vinculador remover funções inteiras e segmentos de dados que não são realmente usados. Há muitas pequenas coisas com as quais você pode mexer para tentar diminuir ainda mais as coisas ou torná-las mais rápidas, mas, em geral, esses são os únicos truques que acabo usando, e qualquer coisa que tenha que ser menor ou mais rápida, eu entregarei -montar.


2

Sim, desabilitar otimizações durante a depuração é uma prática recomendada há algum tempo, por três razões:

  • (a) se você estiver indo para uma etapa do programa com um depurador de alto nível, é um pouco menos confuso.
  • (a) (obsoleto) se você for depurar o programa em uma única etapa com um depurador em linguagem assembly, é muito menos confuso. (Mas por que você se incomodaria com isso quando poderia estar usando um depurador de alto nível?)
  • (b) (obsoleto há muito tempo), você provavelmente executará esse executável específico apenas uma vez, depois fará algumas alterações e recompilará. É uma perda de tempo de uma pessoa esperar 10 minutos extras enquanto o compilador "otimiza" esse executável específico, quando isso economiza menos de 10 minutos de tempo de execução. (Isso não é mais relevante nos PCs modernos que podem compilar um microcontrolador típico executável, com otimização total, em menos de 2 segundos).

Muitas pessoas vão ainda mais nessa direção e enviam afirmações ativadas .


O passo a passo através do código de montagem pode ser muito útil para diagnosticar casos em que o código-fonte realmente especifica algo diferente do que parece (por exemplo, "longvar1 & = ~ 0x40000000; longvar2 & = ~ 0x80000000;") ou onde um compilador gera código de buggy . Eu localizei alguns problemas usando depuradores de código de máquina que eu realmente não acho que poderia ter rastreado de outra maneira.
Supercat

2

Simples: as otimizações demoram muito e podem ser inúteis se você precisar alterar esse trecho de código posteriormente no desenvolvimento. Portanto, eles podem muito bem ser uma perda de tempo e dinheiro.
Eles são úteis para módulos acabados; partes do código que provavelmente não precisarão mais de alterações.


2

certamente faz sentido no caso de pontos de interrupção ... pois o compilador pode remover muitas instruções que não afetam a memória.

considere algo como:

int i =0;

for (int j=0; j < 10; j++)
{
 i+=j;
}
return 0;

pode ser totalmente otimizado (porque inunca é lido). do ponto de vista do seu ponto de interrupção, parece que ele pulou todo esse código, quando ele basicamente nem estava lá ... Presumo que seja por isso que nas funções do tipo sleep você sempre verá algo como:

for (int j=delay; j != 0; j--)
{
    asm( " nop " );
    asm( " nop " );
}
return 0;

1

Se você estiver usando o depurador, eu desabilitaria as otimizações e habilitava a depuração.

Pessoalmente, acho que o depurador PIC causa mais problemas do que me ajuda a corrigir.
Eu apenas uso printf () para USART para depurar meus programas escritos em C18.


1

A maioria dos argumentos contra a ativação da otimização em sua compilação se resume a:

  1. problemas com depuração (conectividade JTAG, pontos de interrupção etc.)
  2. temporização incorreta do software
  3. sh * t para de funcionar corretamente

IMHO os dois primeiros são legítimos, o terceiro nem tanto. Isso geralmente significa que você tem algum código incorreto ou está confiando na exploração insegura da linguagem / implementação ou talvez o autor seja apenas um fã do bom e velho comportamento não definido do tio.

O blog Embedded in Academia tem uma coisa ou duas a dizer sobre comportamento indefinido, e este post é sobre como os compiladores o exploram: http://blog.regehr.org/archives/761


Outro motivo possível é que o compilador pode ser irritantemente lento quando as otimizações estão ativadas.
precisa
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.