Les processeurs / horloges plus rapides peuvent-ils exécuter plus de code?

9

J'écris un programme pour fonctionner sur un ATmega 328 qui fonctionne à 16 MHz (c'est un Arduino Duemilanove si vous les connaissez, c'est une puce AVR).

J'ai un processus d'interruption qui s'exécute toutes les 100 microsecondes. Il est impossible, je dirais, de déterminer la quantité de "code" que vous pouvez exécuter dans une boucle de 100 microsecondes (j'écris en C qui est probablement converti en assemblage puis en une image binaire?).

Cela dépendrait également de la complexité du code (un liner géant pourrait fonctionner plus lentement que plusieurs lignes courtes par exemple).

Ma compréhension est-elle correcte, en ce sens que mon processeur avec une fréquence d'horloge ou 16 MHz effectue 16 millions de cycles par seconde (cela signifie 16 cycles par microseconde 16 000 000/1 000/1 000); Et donc, si je veux faire plus dans ma boucle de 100 microsecondes, acheter un modèle plus rapide comme une version 72Mhz me donnerait 72 cycles par microseconde (72,000,000 / 1,000 / 1,000)?

Actuellement, il fonctionne un peu trop lentement, c'est-à-dire qu'il faut un peu plus de 100 microsecondes pour faire la boucle (combien de temps exactement est trop difficile à dire, mais il prend progressivement du retard) et j'aimerais qu'il fasse un peu plus, est est-ce une approche sensée obtenir une puce plus rapide ou suis-je devenu fou?

jwbensley
la source
.... Un ATmega328 n'est PAS une puce ARM. C'est un AVR.
vicatcu
A bientôt, corrigé!
jwbensley

Réponses:

9

En général, le nombre d'instructions d'assemblage que le dispositif peut exécuter par seconde dépend du mélange d'instructions et du nombre de cycles que chaque type d'instruction prend (CPI) pour exécuter. En théorie, vous pourriez compter votre code en cycle en consultant le fichier asm désassemblé et en regardant la fonction qui vous intéresse, en comptant tous les différents types d'instructions qu'il contient et en recherchant le nombre de cycles à partir de la fiche technique de votre processeur cible.

Le problème de la détermination du nombre effectif d'instructions par seconde est exacerbé dans les processeurs plus complexes par le fait qu'ils sont pipelinés et ont des caches et autres. Ce n'est pas le cas pour un appareil simple comme un ATMega328 qui est une instruction unique dans le processeur de vol.

Quant aux questions pratiques, pour un appareil simple comme un AVR, ma réponse serait plus ou moins "oui". Le doublement de votre vitesse d'horloge devrait réduire de moitié le temps d'exécution d'une fonction donnée. Pour un AVR, cependant, ils ne fonctionneront pas à une vitesse supérieure à 20 MHz, vous ne pourrez donc "overclocker" votre Arduino que de 4 MHz supplémentaires.

Ce conseil ne se généralise pas à un processeur qui a des fonctionnalités plus avancées. Le fait de doubler la vitesse d'horloge de votre processeur Intel ne doublera pas en pratique le nombre d'instructions qu'il exécute par seconde (en raison de fausses prédictions de branche, de ratés de cache, etc.).

vicatcu
la source
Salut, merci pour votre réponse informative! J'en ai vu un ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), vous avez dit qu'un AVR ne peut pas aller plus vite que 20Mhz, pourquoi? La puce sur la carte ci-dessus ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) est un ARM 72Mhz, puis-je m'attendre à une augmentation de performance raisonnable de cela de la manière que j'ai décrite ci-dessus?
jwbensley
2
Le fait de doubler la vitesse de traitement peut ne pas augmenter le débit de vos instructions, car vous pouvez commencer à dépasser la vitesse à laquelle les instructions peuvent être extraites du flash. À ce stade, vous commencez à frapper les "états d'attente Flash" où le processeur s'arrête pendant qu'il attend que l'instruction arrive du flash. Certains microcontrôleurs contournent cela en vous permettant d'exécuter du code à partir de la RAM, ce qui est beaucoup plus rapide que FLASH.
Majenko
@Majenko: drôle, nous avons tous deux fait la même remarque en même temps.
Jason S
Cela arrive ... le vôtre est meilleur que le mien :)
Majenko
1
OK, j'ai marqué la réponse de Vicatcu comme "la réponse". Je pense que c'était le plus approprié en ce qui concerne ma question initiale de vitesse concernant la performance, bien que toutes les réponses soient excellentes et que je suis vraiment étonné par les réponses de tout le monde. Ils m'ont montré que c'est un sujet plus large que je ne l'avais imaginé, et donc, ils m'enseignent tous beaucoup et me donnent beaucoup à rechercher, donc merci à tous: D
jwbensley
8

La réponse de @ vicatcu est assez complète. Une autre chose à noter est que le processeur peut se retrouver dans des états d'attente (cycles de processeur bloqués) lors de l'accès aux E / S, y compris la mémoire de programme et de données.

Par exemple, nous utilisons un TI F28335 DSP; certaines zones de la mémoire RAM sont à l'état d'attente 0 pour le programme et la mémoire de données, donc lorsque vous exécutez du code dans la mémoire RAM, il s'exécute à 1 cycle par instruction (à l'exception des instructions qui prennent plus d'un cycle). Lorsque vous exécutez du code à partir de la mémoire FLASH (EEPROM intégrée, plus ou moins), cependant, il ne peut pas fonctionner à 150 MHz et il est plusieurs fois plus lent.


En ce qui concerne le code d'interruption à grande vitesse, vous devez apprendre un certain nombre de choses.

Tout d'abord, familiarisez-vous avec votre compilateur. Si le compilateur fait du bon travail, il ne devrait pas être beaucoup plus lent que l'assemblage codé à la main pour la plupart des choses. (où "beaucoup plus lent": un facteur 2 me conviendrait; un facteur 10 serait inacceptable) Vous devez apprendre comment (et quand) utiliser les indicateurs d'optimisation du compilateur, et de temps en temps, vous devriez regarder à la sortie du compilateur pour voir comment il fonctionne.

D'autres choses que le compilateur peut faire pour accélérer le code:

  • utiliser des fonctions en ligne (je ne me souviens pas si C le supporte ou si ce n'est qu'un isme C ++), à la fois pour les petites fonctions et pour les fonctions qui ne seront exécutées qu'une ou deux fois. L'inconvénient est que les fonctions en ligne sont difficiles à déboguer, surtout si l'optimisation du compilateur est activée. Mais ils vous épargnent des séquences d'appel / retour inutiles, surtout si l'abstraction "fonction" est à des fins de conception plutôt que d'implémentation de code.

  • Regardez le manuel de votre compilateur pour voir s'il a des fonctions intrinsèques - ce sont des fonctions intégrées dépendantes du compilateur qui correspondent directement aux instructions d'assemblage du processeur; certains processeurs ont des instructions d'assemblage qui font des choses utiles comme inverser min / max / bit et vous pouvez gagner du temps en le faisant.

  • Si vous effectuez un calcul numérique, assurez-vous que vous n'appelez pas les fonctions de bibliothèque de mathématiques inutilement. Nous avons eu un cas où le code ressemblait y = (y+1) % 4à un compteur qui avait une période de 4, s'attendant à ce que le compilateur implémente le modulo 4 en tant que AND au niveau du bit. Au lieu de cela, il a appelé la bibliothèque mathématique. Nous avons donc remplacé par y = (y+1) & 3faire ce que nous voulions.

  • Familiarisez-vous avec la page de piratage de bits . Je vous garantis que vous en utiliserez au moins un souvent.

Vous devez également utiliser les périphériques de temporisation de votre CPU pour mesurer le temps d'exécution du code - la plupart d'entre eux ont un timer / compteur qui peut être réglé pour s'exécuter à la fréquence d'horloge du CPU. Capturez une copie du compteur au début et à la fin de votre code critique, et vous pouvez voir combien de temps cela prend. Si vous ne pouvez pas faire cela, une autre alternative consiste à abaisser une broche de sortie au début de votre code, à la relever à la fin et à regarder cette sortie sur un oscilloscope pour chronométrer l'exécution. Il y a des compromis à chaque approche: le temporisateur / compteur interne est plus flexible (vous pouvez chronométrer plusieurs choses) mais plus difficile à obtenir les informations, tandis que définir / effacer une broche de sortie est immédiatement visible sur une étendue et vous pouvez capturer des statistiques, mais il est difficile de distinguer plusieurs événements.

Enfin, il y a une compétence très importante qui vient avec l'expérience - à la fois générale et avec des combinaisons processeur / compilateur spécifiques: savoir quand et quand ne pas optimiser . En général, la réponse est ne pas optimiser. La citation de Donald Knuth est publiée fréquemment sur StackOverflow (généralement juste la dernière partie):

Il faut oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est à l'origine de tout mal

Mais vous êtes dans une situation où vous savez que vous devez faire une sorte d'optimisation, il est donc temps de mordre la balle et d'optimiser (ou d'obtenir un processeur plus rapide, ou les deux). N'écrivez PAS l'intégralité de votre ISR en assemblage. C'est presque un désastre garanti - si vous le faites, dans les mois ou même les semaines, vous oublierez des parties de ce que vous avez fait et pourquoi, et le code est susceptible d'être très fragile et difficile à changer. Il est probable que certaines parties de votre code soient de bons candidats pour l'assemblage.

Signifie que certaines parties de votre code sont bien adaptées au codage d'assemblage:

  • des fonctions bien contenues et bien définies de petites routines peu susceptibles de changer
  • fonctions pouvant utiliser des instructions de montage spécifiques (min / max / décalage à droite / etc)
  • fonctions qui sont appelées plusieurs fois (vous obtenez un multiplicateur: si vous économisez 0,5usec sur chaque appel, et il est appelé 10 fois, cela vous fait économiser 5 usec, ce qui est important dans votre cas)

Apprenez les conventions d'appel de fonction de votre compilateur (par exemple, où il place les arguments dans les registres et quels registres il enregistre / restaure) afin de pouvoir écrire des routines d'assemblage appelables C.

Dans mon projet actuel, nous avons une base de code assez grande avec du code critique qui doit s'exécuter dans une interruption de 10 kHz (100usec - son familier?) Et il n'y a pas beaucoup de fonctions écrites en assembleur. Ceux qui le sont, sont des choses comme le calcul CRC, les files d'attente logicielles, la compensation de gain / décalage ADC.

Bonne chance!

Jason S
la source
bons conseils sur les techniques empiriques de mesure du temps d'exécution
vicatcu
Une autre excellente réponse à ma question, merci beaucoup Jason S pour ce super morceau de connaissances! Deux choses apparentes après avoir lu ceci; Tout d'abord, je peux augmenter l'interruption de chaque 100uS à 500uS pour donner plus de temps au code pour s'exécuter, je me rends compte maintenant que cela ne me profite pas vraiment d'être aussi rapide. Deuxièmement, je pense que mon code est peut-être trop inefficace, avec un temps d'interruption plus long et un meilleur code, tout pourrait bien se passer. Stackoverflow est un meilleur endroit pour publier le code, donc je le publierai ici et mettrai un lien vers celui-ci ici, si quelqu'un veut jeter un œil et faire des recommandations, veuillez faire: D
jwbensley
5

Une autre chose à noter - il y a probablement des optimisations que vous pouvez effectuer pour rendre votre code plus efficace.

Par exemple - j'ai une routine qui s'exécute à partir d'une interruption de minuterie. La routine doit se terminer en 52 µS, et doit parcourir une grande quantité de mémoire pendant qu'elle le fait.

J'ai réussi une grande augmentation de vitesse en verrouillant la variable de compteur principale dans un registre avec (sur mon µC et compilateur - différent pour le vôtre):

register unsigned int pointer asm("W9");

Je ne connais pas le format de votre compilateur - RTFM, mais vous pourrez faire quelque chose pour accélérer votre routine sans avoir à passer en assembleur.

Cela dit, vous pouvez probablement faire un bien meilleur travail pour optimiser votre routine que le compilateur, donc passer à l'assemblage peut bien vous donner des augmentations de vitesse massives.

Majenko
la source
lol j'ai "simultanément" commenté ma propre réponse concernant le réglage de l'assembleur et l'allocation des registres :)
vicatcu
Si cela prend 100us sur un processeur 16 MHz - c'est évidemment assez énorme, donc c'est beaucoup de code à optimiser. J'ai entendu dire que les compilateurs d'aujourd'hui produisent environ 1,1 fois plus de code qu'un assemblage optimisé à la main. Cela ne vaut vraiment pas la peine pour une telle routine. Pour un rasage de 20% sur une fonction à 6 lignes, peut-être ...
DefenestrationDay
1
Pas nécessairement ... Il pourrait s'agir de seulement 5 lignes de code dans une boucle. Et il ne s'agit pas de la taille du code mais de l' efficacité du code . Vous pourrez peut-être écrire le code différemment, ce qui le rend plus rapide. Je sais pour ma routine d'interruption que j'ai fait. Par exemple, sacrifier la taille pour la vitesse. En exécutant le même code 10 fois en séquence, vous économisez du temps pour que le code fasse la boucle - et les variables de compteur associées. Oui, le code est 10 fois plus long, mais il s'exécute plus rapidement.
Majenko
Salut Majenko, je ne connais pas l'assemblage mais j'avais pensé à l'apprendre, et je pensais que l'Arduino allait être moins compliqué que mon ordinateur de bureau donc cela pourrait être un bon moment pour apprendre, surtout que je veux savoir plus sur ce qui se passe et un niveau inférieur. Comme d'autres l'ont dit, je ne réécrirais pas le tout seulement certaines parties. Ma compréhension est que je peux entrer et sortir de l'ASM dans C, est-ce correct, est-ce ainsi que l'on pourrait réaliser ce mélange de C et ASM? Je posterai sur stackoverflow pour les détails, juste après une idée générale.
jwbensley
@javano: Oui. Vous pouvez entrer et sortir d'ASM dans C. De nombreux systèmes embarqués ont été écrits comme ça - dans un mélange de C et d'assemblage - principalement parce qu'il y avait quelques choses qui ne pouvaient tout simplement pas être faites dans les compilateurs C primitifs disponibles sur le site. temps. Cependant, les compilateurs C modernes tels que gcc (qui est le compilateur utilisé par Arduino) gèrent désormais la plupart et dans de nombreux cas tout ce qui nécessitait auparavant un langage d'assemblage.
davidcary