Les meilleures pratiques pour fonction de gérer 1-256 octets
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:
- 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)
- 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).
- Accepter un unsigned char, et exécuter 256 fois si elle est nulle.
- Avoir une fonction comme celle-ci, mais appelez via les fonctions wrappers qui se comportent comme (0-255) et (256 seulement).
- 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.
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.