Question:
Opportunité d'utiliser des champs de bits dans les structures
anthropomo
2014-10-01 05:19:12 UTC
view on stackexchange narkive permalink

Je dois suivre une grande quantité de données (pour un Arduino) dans un programme tout en m'occupant d'un bon nombre d'autres affaires.

J'ai commencé avec une structure comme celle-ci:

  struct MyStruct {// note: ces noms pourraient aussi bien être foo bar baz uint8_t color; état booléen; zone uint8_t; numéro uint8_t; uint8_t len;};  

J'ai un tableau de 800 d'entre eux. À 5 octets chacun, c'est 4Kb, soit la moitié de la RAM sur l'Arduino Mega.

J'ai changé pour une structure comme celle-ci:

  struct MyStruct {uint8_t color: 3; // valeur max 7 // état uint8_t: 1; *** déplacé, merci Ignacio *** zone uint8_t: 5; // m.v. 31 numéro uint8_t; uint8_t len: 5; // m.v. 31 état uint8_t: 1; // m.v. 1};  

Je comprends que cela réduit la taille de chaque instance à 3 octets, pour un total de 2,4 Ko dans mon tableau.

J'ai lu cela dans certains les implémentations / chipsets utilisant des champs de bits dans les structs peuvent conduire à une exécution moins efficace. Évidemment, c'est une utilisation plus efficace de la mémoire, mais ma question est:

Comment l'Arduino gère-t-il les champs de bits? Sera-ce meilleur, pire ou négligeable en termes ou en vitesse lors de l'itération à travers un tableau de structures de champ de bits? Existe-t-il un bon moyen de tester cela?

Cet ordre utilisera 4 octets. Obtenez ce champ 1 bit entre les 8 autres.
@Ignacio Incroyable ... vous venez de raser 9% de plus sur mon utilisation totale de la mémoire.
La raison pour laquelle les champs de bits dans les structs peuvent conduire à une exécution moins efficace est due aux restrictions d'alignement de mots. Par exemple. s'ils traversent l'alignement naturel 32/64, provoquant des accès multiples. Cela ne devrait pas être un problème pour l'ATmega
La question que vous devez vous poser est de savoir si vous êtes le plus inquiet de manquer de RAM, de mémoire programme ou de cycles de processeur - c'est une situation où vous pouvez optimiser un peu pour l'un au détriment d'un autre et un impact sur le troisième. À un certain moment, vous devriez également demander si un Arduino basé sur ATmega est vraiment la bonne plate-forme.
J'ai pu restructurer le programme pour utiliser l'index du tableau où j'utilisais le nombre de 8 bits, donc jusqu'à 2 octets par structure, un autre 9% rasé. On dirait que je peux épargner les cycles. Je parcours les 800 structures toutes les 50 millisecondes tout en gérant les requêtes http intermittentes. Pas de problème. J'ai repoussé les limites d'ATmega avec celui-ci, mais il me semble que je suis bien en dessous.
Voici quelques lectures supplémentaires que j'ai rencontrées plus tard: http://www.catb.org/esr/structure-packing/
Trois réponses:
jdr5ca
2014-10-01 13:04:39 UTC
view on stackexchange narkive permalink

Ce à quoi vous êtes confronté est le compromis classique temps-mémoire. Les champs de bits seront plus petits en mémoire, mais prendront plus de temps à fonctionner. Vous pouvez compter que quel que soit le processeur, les champs de bits seront plus lents.

Vous utilisez le mot efficace , mais ce mot n'a pas de signification certaine sans une métrique pour ce qui est bon ou mauvais. Lorsque vous n'avez que 8 ko de RAM, l'utilisation de la mémoire est mauvaise, le temps peut être bon marché. Si vous avez des contraintes de temps réel, l'utilisation du temps est mauvaise et la mémoire peut être bon marché. En général, vous ne pouvez acheter que votre moyen de sortir de ce compromis. En d'autres termes, lorsque vous trouvez le temps et la mémoire mauvais, dépensez de l'argent et utilisez une puce plus grosse. Il n'y a pas de réponse unique pour ce qui est bon ou mauvais. C'est en partie pourquoi il y a tant de choix pour les microcontrôleurs, les gens adaptent la puce à l'application et l'application à la puce.

Le remplissage des bits d'un champ de bits sera plus lent que le remplissage d'octets complets. Prenons par exemple

  x = 5; ... asimplestruct.len = x; // vsabitfield.len = x;  

Le premier cas simple sera juste:

  • charger la valeur de x dans un registre
  • le stocker dans l'octet pour len

Le second fait quelque chose comme:

  • charge la valeur actuelle de abitfield
  • charge un masque
  • efface les bits pour len
  • charge la valeur actuelle de x
  • charge un masque
  • efface les bits inutilisés de x
  • décale les bits de x
  • ou de x avec le champ abit
  • stocke la valeur actuelle en mémoire

Si toutes vos opérations regroupent des données dans le champ de bits ou sortent du champ de bits, vous devriez vous attendre à une exécution plus lente. Les champs de bits sont un type de compression - ils coûtent des ticks.

Mais se déplacer dans les champs de bits sera plus rapide car il y a moins d'octets à charger et à stocker. Si vous triez ce tableau, le plus petit nombre d'octets pourrait être un avantage. Si vous les transférez via un port série, la taille compressée du champ de bits pourrait être un gagnant.

Donc en ce qui concerne votre question:

Y a-t-il un bon moyen de tester cela?

Le seul bon moyen de tester cela est d'écrire cas de test pour les deux approches en utilisant un modèle qui correspond étroitement à votre application. Le mélange d'opérations que vous effectuez est vraiment important pour décider si la différence est négligeable ou significative.

Lorsque vous faites ce type d'expérience d'optimisation, utilisez définitivement le contrôle de code source sur votre projet. Vous pouvez créer un référentiel GIT ou Mercurial local en quelques clics. Garder une chaîne de points de contrôle vous permet de déchirer votre code en explorant les effets de différentes implémentations. Si vous prenez un mauvais virage, le référentiel vous permet simplement de revenir au dernier bon point et d'essayer un autre chemin.

(Remarque: cette fois, le compromis mémoire existe également dans la direction opposée. Si vous regardez grâce aux options du compilateur pour les processeurs de classe de bureau, vous trouverez quelque chose appelé emballage de structure . Cette option vous permet d'ajouter des octets vides entre les champs à un octet de sorte qu'ils restent alignés aux limites des mots ou des doubles mots. Cela peut semble fou de jeter la RAM exprès, mais sur les processeurs avec des registres et des bus de 16 ou 32 bits de large, les opérations de mémoire alignées sur mot ou dword peuvent être plus rapides que les opérations par octet.)

JRobert
2014-10-01 22:23:53 UTC
view on stackexchange narkive permalink

Une discussion sur les champs de bits ne serait pas complète sans mentionner que:

Les structures avec des champs de bits peuvent être utilisées comme un moyen portable de tenter de réduire le stockage requis pour une structure (avec le coût probable de l'augmentation de l'espace d'instruction et du temps nécessaires pour accéder aux champs), ou comme moyen non portable de décrire une disposition de stockage connue au niveau du bit.

[Kernighan & Ritchie, Le langage de programmation C, deuxième édition , annexe A-8.3]

D'autres affiches, y compris vous-même, ont abordé le compromis espace / temps. Mais ce qui manque parfois (peut-être pas pertinent pour votre application -?) Est la dépendance à l'implémentation complète de la disposition du stockage . Ceci est important lorsque les applications partagent ces données avec un autre processus: matériel de lecture ou d'écriture, dont les affectations de bits de registre sont nécessairement fixes; communiquer les données à un autre système (via un réseau ou un périphérique de stockage n'a pas d'importance); ou à une autre application sur le même système, qui peut avoir été compilée avec un compilateur ou une version différente du compilateur.

soerium
2014-10-01 16:26:33 UTC
view on stackexchange narkive permalink

Un petit conseil - je vois que la structure n'est pas un 1B aligné pour un processeur 8 bits. À la fin de votre struct, vous pouvez ajouter padding longueur 2 bits ou repenser l'augmentation de la taille de state

  struct MyStruct {uint8_t color : 3; // valeur max 7 // état uint8_t: 1; *** déplacé, merci Ignacio *** zone uint8_t: 5; // m.v. 31 numéro uint8_t; uint8_t len: 5; // m.v. 31 état uint8_t: 1; // m.v. 1 remplissage uint8_t: 2;};  

L'ajout du remplissage ne modifie pas la mémoire dynamique au moment de la compilation. Pardonnez mon ignorance ici, mais il semble que le compilateur s'occupe du remplissage. Ou quelque chose se passe-t-il au moment de l'exécution pour compenser si je n'inclus pas le remplissage?
C'est vrai. Dans ce cas, cela ressemble plus à une bonne pratique.


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...