Processadores / relógios mais rápidos podem executar mais código?


9

Estou escrevendo um programa para rodar em um ATmega 328 que roda em 16Mhz (é um Arduino Duemilanove, se você os conhece, é um chip AVR).

Eu tenho um processo de interrupção executando a cada 100 microssegundos. É impossível, eu diria, calcular quanto "código" você pode executar em um loop de 100 microssegundos (estou escrevendo em C, que presumivelmente é convertido em montagem e depois em uma imagem binária?).

Além disso, isso dependeria da complexidade do código (um liner gigante pode ser mais lento que várias linhas curtas, por exemplo).

Meu entendimento está correto, pois meu processador com uma taxa de clock ou 16Mhz executa 16 milhões de ciclos por segundo (isso significa 16 ciclos por microssegundo 16.000.000 / 1.000 / 1.000); E assim, se eu quiser fazer mais no meu loop de 100 microssegundos, comprar um modelo mais rápido como uma versão de 72Mhz me daria 72 ciclos por microssegundo (72.000.000 / 1.000 / 1.000)?

Atualmente, ele é um pouco lento demais, ou seja, está demorando um pouco mais de 100 microssegundos para fazer o loop (quanto tempo exatamente é muito difícil de dizer, mas gradualmente fica para trás) e eu gostaria que fizesse um pouco mais Esta é uma abordagem sã, ficando um chip mais rápido ou eu enlouqueci?


.... Um ATmega328 NÃO é um chip ARM. É um AVR.
vicatcu

Saúde, corrigido!
jwbensley

Respostas:


9

Em geral, o número de instruções de montagem que o dispositivo pode executar por segundo dependerá da combinação de instruções e de quantos ciclos cada tipo de instrução leva (CPI) para executar. Em teoria, você poderia contar o código do ciclo olhando o arquivo asm desmontado e a função que lhe interessa, contando todos os diferentes tipos de instruções nele e olhando as contagens do ciclo na folha de dados do seu processador de destino.

O problema de determinar o número efetivo de instruções por segundo é exacerbado em processadores mais complexos pelo fato de serem pipelined e terem caches e o que não. Este não é o caso de um dispositivo simples como um ATMega328, que é uma única instrução no processador de vôo.

Quanto a questões práticas, para um dispositivo simples como um AVR, minha resposta seria mais ou menos "sim". Dobrar a velocidade do relógio deve ter metade do tempo de execução de qualquer função. Para um AVR, no entanto, eles não rodam mais rápido que 20MHz, então você só pode "fazer overclock" do seu Arduino em outros 4MHz.

Este conselho não generaliza para um processador que possui recursos mais avançados. Dobrar a velocidade do relógio no processador Intel, na prática, não dobrará o número de instruções que ele executa por segundo (devido a erros de previsão de ramificação, falhas de cache e assim por diante).


Olá, obrigado pela sua resposta informativa! Eu já vi um desses ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), você disse que um AVR não pode ir mais rápido que 20Mhz, por que isso? O chip na placa acima ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) é um ARM de 72Mhz, eu poderia esperar um aumento de desempenho razoável com isso da maneira que descrevi acima?
jwbensley

2
Dobrar a velocidade de processamento pode não aumentar a taxa de transferência de instruções, pois você pode começar a exceder a velocidade na qual as instruções podem ser buscadas no flash. Nesse ponto, você começa a pressionar "Flash wait states" (estados de espera do flash), onde a CPU faz uma pausa enquanto aguarda a chegada da instrução do flash. Alguns microcontroladores contornam isso, permitindo que você execute código da RAM, que é muito mais rápido que o FLASH.
Majenko 28/10

@Majenko: engraçado, nós dois fizemos o mesmo ponto ao mesmo tempo.
Jason S

Acontece ... o seu é melhor que a minha :)
Majenko

11
OK, marquei a resposta de Vicatcu como "a resposta". Eu acho que foi o mais apropriado em relação à minha pergunta original de velocidade relacionada ao desempenho, embora todas as respostas sejam ótimas e estou realmente entusiasmado com as respostas de todos. Eles têm me mostrado que é um assunto mais amplo do que eu percebi, e assim, eles são todos os lotes ensinando-me e dando-me lotes para a investigação, então obrigado a todos: D
jwbensley

8

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) % 4com 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) & 3fazer 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!


bons conselhos sobre técnicas de medição de tempo de execução empírica
vicatcu

Outra ótima resposta para a minha pergunta, muito obrigado Jason S por esse incrível pedaço de conhecimento! Duas coisas aparentes depois de ler isso; Em primeiro lugar, posso aumentar a interrupção de cada 100uS para 500uS para dar mais tempo ao código para executar, percebo que agora isso não está me beneficiando por ser tão rápido. Em segundo lugar, acho que meu código talvez seja ineficiente, com o tempo de interrupção mais longo e um código melhor, tudo pode ficar bem. Stackoverflow é um lugar melhor para postar o código, por isso vou postá-lo lá e colocar um link para ele aqui, se alguém quiser ter um olhar e fazer as recomendações agradar fazer: D
jwbensley

5

Outra coisa a ser observada - provavelmente há algumas otimizações que você pode executar para tornar seu código mais eficiente.

Por exemplo - eu tenho uma rotina que é executada a partir de uma interrupção do timer. A rotina precisa ser concluída em 52µS e precisa percorrer uma grande quantidade de memória enquanto o faz.

Consegui um grande aumento de velocidade bloqueando a variável principal do contador em um registro com (no meu µC e compilador - diferente do seu):

register unsigned int pointer asm("W9");

Não sei o formato do seu compilador - RTFM, mas haverá algo que você pode fazer para tornar sua rotina mais rápida sem precisar mudar para montagem.

Dito isso, você provavelmente pode fazer um trabalho muito melhor na otimização de sua rotina do que o compilador, portanto, mudar para montagem pode muito bem proporcionar aumentos de velocidade maciços.


lol eu "ao mesmo tempo", comentou sobre a minha própria resposta sobre o ajuste assembler e registrar alocação :)
vicatcu

Se estiver levando 100us em um processador de 16 MHz - é obviamente muito grande, então há muito código para otimizar. Ouvi dizer que os compiladores de hoje produzem cerca de 1,1 vezes o código que o assembly otimizado à mão. Totalmente não vale a pena por uma rotina tão grande. Para raspar 20% de desconto em função 6 linha, talvez ...
DefenestrationDay

11
Não necessariamente ... Pode haver apenas 5 linhas de código em um loop. E não se trata do tamanho do código, mas da eficiência do código . Você pode escrever o código de maneira diferente, tornando-o mais rápido. Eu sei pela minha rotina de interrupção que eu fiz. Por exemplo, sacrificando o tamanho pela velocidade. Ao executar o mesmo código 10 vezes em sequência, você economiza o tempo de código para executar o loop - e as variáveis ​​de contador associadas. Sim, o código é 10 vezes mais longo, mas é executado mais rapidamente.
Majenko 29/10

Oi Majenko, eu não sei montagem, mas eu estava pensando em aprender e estava pensando que o Arduino será menos complicado que o meu computador de mesa, portanto, este é um bom momento para aprender, especialmente como eu quero saber mais sobre o que está acontecendo e um nível mais baixo. Como outros já disseram, eu não reescreveria a coisa toda apenas em algumas partes. Meu entendimento é que eu posso entrar e sair do ASM dentro de C, isso está correto? É assim que se pode alcançar esse mix de C e ASM? Vou postar no stackoverflow para os detalhes, logo após uma idéia geral.
jwbensley

@javano: Sim. Você pode entrar e sair do ASM dentro do C. Muitos sistemas embarcados foram escritos assim - em uma mistura de C e montagem - principalmente porque havia algumas coisas que simplesmente não podiam ser feitas nos compiladores C primitivos disponíveis no Tempo. No entanto, os compiladores C modernos, como o gcc (que é o compilador usado pelo Arduino), agora lidam com a maioria e, em muitos casos, com tudo o que costumava exigir linguagem assembly.
davidcary
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.