A resposta de @ vicatcu é bastante abrangente. Uma coisa adicional a ser observada é que a CPU pode ficar em estado de espera (ciclos de CPU paralisados) ao acessar a E / S, incluindo programa e memória de dados.
Por exemplo, estamos usando um TI F28335 DSP; algumas áreas da RAM são estado de espera 0 para memória de programa e dados; portanto, quando você executa o código na RAM, ele é executado a 1 ciclo por instrução (exceto as instruções que demoram mais de 1 ciclo). Quando você executa código da memória FLASH (EEPROM embutida, mais ou menos), no entanto, ele não pode ser executado a 150MHz completos e é várias vezes mais lento.
Com relação ao código de interrupção de alta velocidade, você deve aprender várias coisas.
Primeiro, familiarize-se com seu compilador. Se o compilador faz um bom trabalho, não deve ser muito mais lento que o conjunto codificado manualmente para a maioria das coisas. (onde "muito mais lento": um fator de 2 seria bom para mim; um fator de 10 seria inaceitável) Você precisa aprender como (e quando) usar os sinalizadores de otimização do compilador e, de vez em quando, deve procurar na saída do compilador para ver como ele funciona.
Algumas outras coisas que você pode fazer o compilador para acelerar o código:
use funções embutidas (não lembro se o C suporta isso ou se é apenas um C ++), tanto para funções pequenas quanto para funções que serão executadas apenas uma ou duas vezes. A desvantagem é que as funções embutidas são difíceis de depurar, especialmente se a otimização do compilador estiver ativada. Mas eles economizam seqüências desnecessárias de chamada / retorno, especialmente se a abstração da "função" é para fins de design conceitual, e não para implementação de código.
Veja o manual do compilador para ver se ele possui funções intrínsecas - essas são funções internas dependentes do compilador que são mapeadas diretamente para as instruções de montagem do processador; alguns processadores possuem instruções de montagem que fazem coisas úteis como min / max / bit reverse e você pode economizar tempo fazendo isso.
Se você estiver fazendo cálculos numéricos, verifique se não está chamando as funções da biblioteca de matemática desnecessariamente. Tivemos um caso em que o código era algo parecido y = (y+1) % 4
com um contador que tinha um período de 4, esperando que o compilador implementasse o módulo 4 como um AND bit a bit. Em vez disso, chamou a biblioteca de matemática. Então substituímos por y = (y+1) & 3
fazer o que queríamos.
Familiarize-se com a página de hackers que movimentam os bits . Eu garanto que você usará pelo menos um desses com frequência.
Você também deve usar o (s) periférico (s) de timer da CPU para medir o tempo de execução do código - a maioria deles possui um timer / contador que pode ser configurado para ser executado na frequência do clock da CPU. Capture uma cópia do contador no início e no final do seu código crítico e você poderá ver quanto tempo leva. Se você não puder fazer isso, outra alternativa é abaixar um pino de saída no início do seu código e aumentá-lo no final, e observe essa saída em um osciloscópio para cronometrar a execução. Existem vantagens e desvantagens em cada abordagem: o cronômetro / contador interno é mais flexível (é possível cronometrar várias coisas), mas é mais difícil obter as informações, enquanto definir / limpar um pino de saída é imediatamente visível em um escopo e você pode capturar estatísticas, mas é difícil distinguir vários eventos.
Finalmente, há uma habilidade muito importante que vem com a experiência - geral e com combinações específicas de processador / compilador: saber quando e quando não otimizar . Em geral, a resposta é não otimizar. A citação de Donald Knuth é publicada com freqüência no StackOverflow (geralmente apenas a última parte):
Devemos esquecer pequenas eficiências, digamos, 97% das vezes: a otimização prematura é a raiz de todo mal
Mas você está em uma situação em que sabe que precisa fazer algum tipo de otimização, então é hora de acertar e otimizar (ou obter um processador mais rápido, ou ambos). Você não escrever todo o seu ISR na montagem. Isso é quase um desastre garantido - se você fizer isso, dentro de meses ou até semanas você esquecerá partes do que fez e por quê, e o código provavelmente será muito quebradiço e difícil de mudar. Não é provável que sejam partes de seu código, no entanto, que são bons candidatos para a montagem.
Sinais de que partes do seu código são adequadas para codificação de montagem:
- funções que são pequenas rotinas bem contidas e bem definidas, improváveis de mudar
- funções que podem utilizar instruções de montagem específicas (min / max / shift direito / etc)
- funções que são chamadas várias vezes (você obtém um multiplicador: se você salvar 0,5 usec em cada chamada e ela for chamada 10 vezes, você economizará 5 usec, o que é significativo no seu caso)
Aprenda as convenções de chamada de função do seu compilador (por exemplo, onde ele coloca os argumentos nos registros e quais registros ele salva / restaura) para que você possa escrever rotinas de montagem que podem ser chamadas por C.
No meu projeto atual, temos uma base de código bastante grande com código crítico que precisa ser executado em uma interrupção de 10kHz (100usec - soa familiar?) E não existem muitas funções escritas em assembly. Os que são, são coisas como cálculo de CRC, filas de software, compensação de ganho / compensação de ADC.
Boa sorte!