Question

J'ai quelques fonctions qui sont conçues pour gérer 1-256 octets, en cours d'exécution sur une plate-forme C embarqué où le passage d'un octet est beaucoup plus rapide et plus compact que le passage d'un int (une instruction contre trois), ce qui est le meilleur moyen de coder:

  1. Accepter un int, sortie précoce si zéro, et sinon copier le bit de poids faible de la valeur de comptage à un unsigned char et l'utilisation que dans un do {} while (- nombre); boucle (une valeur de paramètre de 256 va se convertir à 0, mais sera capable de 256 fois)
  2. Accepter un unsigned char, sortie précoce si zéro, et une version spéciale de la fonction de 256 octets (ces cas seront connus à l'avance).
  3. Accepter un unsigned char, et exécuter 256 fois si elle est nulle.
  4. Avoir une fonction comme celle-ci, mais appelez via les fonctions wrappers qui se comportent comme (0-255) et (256 seulement).
  5. Avoir une fonction comme celle-ci, mais appelez via des macros wrapper qui se comportent comme (0-255) et (256 seulement).

Il est prévu que la boucle interne de la fonction représentent probablement 15% à 30% du temps d'exécution du processeur lorsque le système est occupé; il sera parfois utilisé pour un petit nombre d'octets, et parfois pour les grands. La puce de mémoire utilisée par la fonction a une surcharge par transaction, et je préfère avoir ma fonction mémoire à accès faire le démarrage transaction / do-stuff / séquence de fin transaction interne.

Le code le plus efficace serait d'accepter simplement un unsigned char et considérer une valeur de paramètre de 0 comme une demande de faire 256 octets, en se fondant sur l'appelant pour éviter toute tentative accidentelle de lire 0 octets. Cela semble un peu dangereux, cependant. Ont d'autres portaient sur des questions sur les systèmes embarqués? Comment ont-ils été traités?

modifier La plate-forme est un PIC18Fxx (128K espace de code; 3.5K RAM), la connexion à une puce de mémoire flash SPI; lecture 256 octets en moins sont prévus pourraient potentiellement lire dépassement des tampons dans le PIC. Écriture 256 octets au lieu de 0 données corrompues dans le feriez la puce flash. Le port SPI PIC est limitée à un octet tous les 12 fois d'instruction si l'on ne vérifie pas l'état d'occupation; il sera plus lente si l'on fait. Une opération d'écriture typique nécessite l'envoi de 4 octets en plus des données à recevoir; une lecture nécessite un octet supplémentaire pour « retournement de SPI » (la plus rapide pour accéder au port SPI est de lire le dernier octet juste avant d'envoyer la suivante).

Le compilateur est HiTech PICC-18std.

J'ai généralement aimé PICC-16 compilateurs du HiTech; HiTech semble avoir détourné leurs énergies loin du produit PICC-18std vers leur ligne PICC-18pro qui a le temps de compilation encore plus lents, semble nécessiter l'utilisation de pointeurs 3 octets « const » plutôt que des pointeurs à deux octets, et a son propres idées sur l'allocation de mémoire. Peut-être que je devrais regarder plus au PICC-18pro, mais quand j'ai essayé de compiler mon projet sur une version eval de PICC-18pro cela n'a pas fonctionné et je ne figure pas exactement pourquoi - peut-être quelque chose sur la mise en page variable n'accord avec mes routines asm -. Je viens utilisèrent des PICC-18std

Soit dit en passant, je viens de découvrir que PICC-18 aime particulièrement faire {} while (- bytevar); et plus particulièrement dégoûts do {} while (- IntVar); Je me demande ce qui se passe dans « l'esprit » du compilateur quand il génère ce dernier?

  do
  {
    local_test++;
    --lpw;
  } while(lpw);

  2533                           ;newflashpic.c: 792: do
  2534                           ;newflashpic.c: 793: {
  2535  0144A8  2AD9                incf    fsr2l,f,c
  2536                           ;newflashpic.c: 795: } while(--lpw);
  2537  0144AA  0E00                movlw   low ?_var_test
  2538  0144AC  6EE9                movwf   fsr0l,c
  2539  0144AE  0E01                movlw   high ?_var_test
  2540  0144B0  6EEA                movwf   fsr0h,c
  2541  0144B2  06EE                decf    postinc0,f,c
  2542  0144B4  0E00                movlw   0
  2543  0144B6  5AED                subwfb  postdec0,f,c
  2544  0144B8  50EE                movf    postinc0,w,c
  2545  0144BA  10ED                iorwf   postdec0,w,c
  2546  0144BC  E1F5                bnz l242

Les charges du compilateur un pointeur à la variable, même en utilisant l'instruction LFSR (qui prendrait deux mots), mais une combinaison de movlw / MOVWF (en quatre). Ensuite, il utilise ce pointeur pour faire la décrémentation et comparer. Même si je dois admettre que do {} while (- wordvar); ne peut pas céder code agréable que do {} while (wordvar--); le code est mieux que ce que le format ci génère effectivement. Faire un décrément séparé et tout test (par exemple en (--lpw, LPW)) donne le code sensible, mais il semble un peu laid. L'opérateur post-décrément pourrait donner le meilleur code pour une boucle de décomptage:

  decf _lpw
  btfss _STATUS,0 ; Skip next inst if carry (i.e. wasn't zero)
   decf _lpw+1
  bc    loop  ; Carry will be clear only if lpw was zero

mais il génère plutôt pire que le code --lpw. le bcode is serait pour une boucle de comptage jusqu'à:

  infsnz  _lpw
   incfsz _lpw+1
   bra loop

mais le compilateur ne génère pas.

EDIT 2 Une autre approche que je pourrais utiliser: allouer une variable de 16 bits global pour le nombre d'octets, et d'écrire les fonctions de telle sorte que le compteur est toujours remis à zéro avant la sortie. Ensuite, si seulement une valeur de 8 bits est nécessaire, il ne serait nécessaire de charger 8 bits. J'utiliser des macros pour des trucs afin qu'ils puissent être peaufinés pour une meilleure efficacité. Sur le PIC, en utilisant | = sur une variable qui est connu pour être nul n'est jamais plus lent que =, et est parfois plus rapide. Par exemple, IntVar | = 15 ou IntVar | = 0x300 aurait deux instructions (chaque cas a seulement à se soucier de un octet du résultat et peut ignorer l'autre); IntVar | = 4 (ou une puissance de 2) est une instruction. Il est évident que sur d'autres processeurs, IntVar = 0x300 serait plus rapide que IntVar | = 0x300; si j'utiliser une macro, il pourrait être modifié selon le cas.

Était-ce utile?

La solution

FWIW, je choisirais une variante de l'option # 1. L'interface de la fonction reste sensible, intuitive, et semble moins susceptible d'être appelé à tort (vous voudrez peut-être penser à ce que vous voulez faire si une valeur supérieure à 256 est passé en - pourrait être approprié une affirmation debug-build uniquement).

Je ne pense pas que le mineur « hack » / micro-optimisation pour boucler le nombre de fois à l'aide d'un compteur à 8 bits serait vraiment un problème de maintenance, et ce que vous avez fait une analyse considérable semble le justifier.

Je ne discuterai pas contre wrappers si quelqu'un les a préféré, mais je préfère personnellement pencher vers l'option 1 toujours aussi peu.

Cependant, je dirais contre avoir l'interface publique nécessitent que l'appelant passer une valeur moins qu'ils ne voulaient lire.

Autres conseils

Votre fonction intérieure doit copier count + 1 octets, par exemple,

 do /* copy one byte */ while(count-- != 0);

Si les autres alternatives post-décrément est lente, sont:

 ... /* copy one byte */
 while (count != 0) { /* copy one byte */; count -= 1; }

ou

 for (;;) { /* copy one byte */; if (count == 0) break; count -= 1; }

L'appelant / emballage peut faire:

if (count > 0 && count <= 256) inner((uint8_t)(count-1))

ou

if (((unsigned )(count - 1)) < 256u) inner((uint8_t)(count-1))

si son plus rapide dans votre compilateur.

Si un paramètre instructions coûts int 3 et un coût paramètre char 1, vous pouvez passer un paramètre supplémentaire char pour le 1 bit supplémentaire vous manque. Il semble assez stupide que votre (probablement 16 bits) int prend plus de deux fois plus d'instructions comme un omble chevalier 8 bits.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top