web-dev-qa-db-fra.com

Embedded C ++: utiliser STL ou non?

J'ai toujours été ingénieur logiciel embarqué, mais généralement sur la couche 3 ou 2 de la pile OSI. Je ne suis pas vraiment un mec du hardware. J'ai généralement toujours fait des produits de télécommunications, généralement des téléphones portables/portables, ce qui signifie généralement quelque chose comme un processeur ARM 7.

Maintenant, je me retrouve dans un monde intégré plus générique, dans une petite start-up, où je pourrais passer à des processeurs "pas si puissants" (il y a le bit subjectif) - je ne peux pas prédire lequel.

J'ai lu pas mal de débats sur l'utilisation de STL en C++ dans les systèmes embarqués et il n'y a pas de réponse claire. Il y a quelques petites inquiétudes sur la portabilité, et quelques-unes sur la taille du code ou l'exécution, mais j'ai deux préoccupations majeures:
1 - gestion des exceptions; Je ne sais toujours pas s'il faut l'utiliser (voir Embedded C++: pour utiliser des exceptions ou non? )
2 - Je n'aime pas du tout l'allocation dynamique de mémoire dans les systèmes embarqués, en raison des problèmes que cela peut entraîner. J'ai généralement un pool de tampons qui est alloué statiquement au moment de la compilation et qui ne sert que des tampons de taille fixe (s'il n'y a pas de tampons, réinitialisation du système). La STL, bien sûr, fait beaucoup d'allocation dynamique.

Maintenant, je dois prendre la décision d'utiliser ou de renoncer à la STL - pour toute l'entreprise, pour toujours (cela va dans certains s/w très fondamentaux).

De quelle façon dois-je sauter? Super-sûr et perdre une grande partie de ce qui constitue C++ (imo, c'est plus que la définition du langage) et peut-être rencontrer des problèmes plus tard ou devoir ajouter beaucoup de gestion des exceptions et peut-être un autre code maintenant?

Je suis tenté d'aller avec Boost , mais 1) je ne suis pas sûr qu'il portera sur tous les processeurs intégrés que je pourrais vouloir utiliser et 2) sur leur site Web, ils disent qu'ils ne le font pas garantir/recommander certaines parties de celui-ci pour les systèmes embarqués (en particulier les FSM, ce qui semble bizarre). Si je choisis Boost et que nous trouvons un problème plus tard ....

70
Mawg

Super-sûr et perdre une grande partie de ce qui constitue C++ (imo, c'est plus que la définition du langage) et peut-être rencontrer des problèmes plus tard ou devoir ajouter beaucoup de gestion des exceptions et peut-être un autre code maintenant?

Nous avons un débat similaire dans le monde du jeu et les gens descendent des deux côtés. En ce qui concerne la partie citée, pourquoi craindriez-vous de perdre "une grande partie de ce qui constitue C++"? Si ce n'est pas pragmatique, ne l'utilisez pas. Peu importe que ce soit "C++" ou non.

Exécutez des tests. Pouvez-vous contourner la gestion de la mémoire de STL d'une manière qui vous satisfasse? Si oui, en valait-il la peine? Beaucoup de problèmes STL et boost sont conçus pour résoudre tout simplement ne se posent pas si vous concevez pour éviter l'allocation dynamique de mémoire au hasard ... STL résout-il un problème spécifique auquel vous êtes confronté?

Beaucoup de gens ont abordé la STL dans des environnements restreints et en ont été satisfaits. Beaucoup de gens l'évitent. Certaines personnes proposent normes entièrement nouvelles . Je ne pense pas qu'il y ait une bonne réponse.

33
Dan Olson

Je travaille quotidiennement sur des systèmes embarqués en temps réel. Bien sûr, ma définition du système embarqué peut être différente de la vôtre. Mais nous utilisons pleinement la STL et les exceptions et nous ne rencontrons aucun problème ingérable. Nous utilisons également de la mémoire dynamique (à un taux très élevé; en allouant beaucoup de paquets par seconde, etc.) et nous n'avons pas encore eu besoin de recourir à des allocateurs ou des pools de mémoire personnalisés. Nous avons même utilisé C++ dans les gestionnaires d'interruption. Nous n'utilisons pas de boost, mais uniquement parce qu'une certaine agence gouvernementale ne nous le permet pas.

D'après notre expérience, vous pouvez en effet utiliser de nombreuses fonctionnalités C++ modernes dans un environnement intégré tant que vous utilisez votre tête et effectuez vos propres tests de performance. Je vous recommande fortement de faire usage de Scott Meyer Effective C++ 3e édition ainsi que de Sutter et Alexandrescu C++ Coding Standards pour vous aider à utiliser C++ avec un style de programmation sain.

Edit: Après avoir obtenu un vote positif sur ce 2 ans plus tard, permettez-moi de poster une mise à jour. Nous sommes beaucoup plus avancés dans notre développement et nous avons finalement atteint des points dans notre code où les conteneurs de bibliothèque standard sont trop lents dans des conditions de haute performance. Ici, nous avons en fait eu recours à des algorithmes personnalisés, à des pools de mémoire et à des conteneurs simplifiés. C'est la beauté du C++, vous pouvez utiliser la bibliothèque standard et obtenir toutes les bonnes choses qu'elle fournit pour 90% de vos cas d'utilisation. Vous ne jetez pas tout lorsque vous rencontrez des problèmes, vous optimisez simplement les points chauds à la main.

44
Brian Neal

Les autres articles ont abordé les questions importantes de l'allocation dynamique de mémoire, des exceptions et du possible ballonnement du code. Je veux juste ajouter: N'oubliez pas <algorithm>! Que vous utilisiez des vecteurs STL ou des tableaux et pointeurs C simples, vous pouvez toujours utiliser sort(), binary_search(), random_shuffle(), les fonctions de création et de gestion des tas, etc. Ces routines seront presque certainement plus rapides et moins boguées que les versions que vous construisez vous-même.

Exemple: à moins d'y réfléchir attentivement, un algorithme de lecture aléatoire que vous construisez vous-même est susceptible de produire des distributions asymétriques ; random_shuffle() ne le fera pas.

25
j_random_hacker

Electronic Arts a écrit n long traité sur les raisons pour lesquelles la STL était inappropriée pour le développement de consoles embarquées et pourquoi ils devaient écrire les leurs. C'est un article détaillé, mais les raisons les plus importantes étaient:

  1. Les allocateurs STL sont lents, gonflés et inefficaces
  2. Les compilateurs ne sont en fait pas très bons pour intégrer tous ces appels de fonction profonds
  3. Les allocateurs STL ne prennent pas en charge l'alignement explicite
  4. Les algorithmes STL fournis avec GCC et STL de MSVC ne sont pas très performants, car ils sont très indépendants de la plate-forme et manquent donc beaucoup de microoptimisations qui peuvent faire une grande différence.

Il y a quelques années, notre entreprise a pris la décision de ne pas utiliser du tout la STL, mais a plutôt implémenté notre propre système de conteneurs qui sont au maximum performants, plus faciles à déboguer et plus conservateurs de mémoire. Cela a été beaucoup de travail, mais il s'est souvent remboursé. Mais le nôtre est un espace dans lequel les produits rivalisent sur la quantité qu'ils peuvent entasser en 16,6 ms avec un processeur et une taille de mémoire donnés.

Quant aux exceptions: elles sont lentes sur les consoles, et quiconque vous dit le contraire n'a pas essayé de les chronométrer. La simple compilation avec eux activés ralentira l'ensemble du programme en raison du code prolog/epilog nécessaire - mesurez-le vous-même si vous ne me croyez pas. C'est encore pire sur les processeurs en ordre que sur le x86. Pour cette raison, le compilateur que nous utilisons ne prend même pas en charge les exceptions C++.

Le gain de performances n'est pas tant en évitant le coût d'un lancement d'exception - c'est en désactivant complètement les exceptions.

19
Crashworks

Permettez-moi de commencer en disant que je n'ai pas fait de travail intégré depuis quelques années, et jamais en C++, donc mes conseils valent chaque centime que vous payez pour cela ...

Les modèles utilisés par STL ne généreront jamais de code que vous n'auriez pas besoin de générer vous-même, donc je ne m'inquiéterais pas de la surcharge de code.

La STL ne lève pas d'exceptions par elle-même, donc cela ne devrait pas être un problème. Si vos classes ne lancent pas, vous devriez être en sécurité. Divisez l'initialisation de votre objet en deux parties, laissez le constructeur créer un objet bare bones, puis effectuez toute initialisation qui pourrait échouer dans une fonction membre qui renvoie un code d'erreur.

Je pense que toutes les classes de conteneurs vous permettront de définir votre propre fonction d'allocation, donc si vous souhaitez allouer à partir d'un pool, vous pouvez y arriver.

15
Mark Ransom

Le projet open source "Embedded Template Library (ETL)" cible les problèmes habituels avec le STL utilisé dans les applications embarquées en fournissant/implémentant une bibliothèque:

  • comportement déterministe
  • "Créez un ensemble de conteneurs dont la taille ou la taille maximale est déterminée au moment de la compilation. Ces conteneurs doivent être largement équivalents à ceux fournis dans la STL, avec une API compatible."
  • pas d'allocation de mémoire dynamique
  • aucun RTTI requis
  • peu d'utilisation des fonctions virtuelles (uniquement lorsque cela est absolument nécessaire)
  • ensemble de conteneurs à capacité fixe
  • mémoire cache conviviale des conteneurs en tant que bloc de mémoire alloué en continu
  • taille de code conteneur réduite
  • énumérations intelligentes typesafe
  • Calculs CRC
  • sommes de contrôle et fonctions de hachage
  • variantes = type de raccords sûrs
  • Choix d'assertions, d'exceptions, de gestionnaire d'erreurs ou pas de vérification des erreurs
  • fortement testé à l'unité
  • code source bien documenté
  • et d'autres fonctionnalités ...

Vous pouvez également envisager une publicité C++ STL for Embedded Developers fourni par E.S.R. Labs.

5
thinwybk
  1. pour la gestion de la mémoire, vous pouvez implémenter votre propre allocateur, qui demande de la mémoire au pool. Et tous les conteneurs STL ont un modèle pour l'allocateur.

  2. par exception, STL ne lève pas beaucoup d'exceptions, en général, les plus courantes sont: par manque de mémoire, dans votre cas, le système devrait se réinitialiser, donc vous pouvez faire une réinitialisation dans l'allocateur. d'autres sont comme hors de portée, vous pouvez l'éviter par l'utilisateur.

  3. donc, je pense que vous pouvez utiliser STL dans le système embarqué :)

5
ddh

Cela dépend essentiellement de votre compilateur et de la quantité de mémoire dont vous disposez. Si vous avez plus de quelques Kb de RAM, avoir l'allocation dynamique de mémoire aide beaucoup. Si l'implémentation de malloc à partir de la bibliothèque standard que vous avez n'est pas ajustée à votre taille de mémoire, vous pouvez écrire le vôtre, ou il y a de beaux exemples comme mm_malloc de Ralph Hempel que vous pouvez utiliser pour écrire vos nouveaux opérateurs et supprimer des opérateurs en haut.

Je ne suis pas d'accord avec ceux qui répètent le mème que les exceptions et les conteneurs stl sont trop lents ou trop gonflés, etc. Bien sûr, cela ajoute un peu plus de code qu'un simple malloc de C, mais une utilisation judicieuse des exceptions peut rendre le code beaucoup plus clair et éviter trop d'erreurs lors de la vérification du texte de présentation en C.

Il faut garder à l'esprit que les allocateurs STL augmenteront leurs allocations en puissance de deux, ce qui signifie parfois qu'il effectuera des réaffectations jusqu'à ce qu'il atteigne la taille correcte, ce que vous pouvez empêcher avec reserve il devient donc aussi bon marché qu'un malloc de la taille souhaitée si vous connaissez la taille à allouer de toute façon.

Si vous avez un gros tampon dans un vecteur par exemple, à un moment donné, il peut faire une réallocation et finit par utiliser jusqu'à 1,5 fois la taille de la mémoire que vous prévoyez d'utiliser à un moment donné lors de la réallocation et du déplacement des données. (Par exemple, à un moment donné, il a N octets alloués, vous ajoutez des données via append ou un itérateur d'insertion et il alloue 2N octets, copie le premier N et libère N. Vous avez 3N octets alloués à un moment donné).

En fin de compte, cela présente de nombreux avantages et est payant si vous savez ce que vous faites. Vous devriez savoir un peu comment C++ fonctionne pour l'utiliser sans surprise sur des projets intégrés.

Et pour le gars des tampons fixes et de la réinitialisation, vous pouvez toujours réinitialiser à l'intérieur du nouvel opérateur ou quoi que ce soit si vous manquez de mémoire, mais cela signifierait que vous avez fait une mauvaise conception qui peut épuiser votre mémoire.

Une exception levée avec ARM realview 3.1:

--- OSD\#1504 throw fapi_error("OSDHANDLER_BitBlitFill",res);
   S:218E72F0 E1A00000  MOV      r0,r0
   S:218E72F4 E58D0004  STR      r0,[sp,#4]
   S:218E72F8 E1A02000  MOV      r2,r0
   S:218E72FC E24F109C  ADR      r1,{pc}-0x94 ; 0x218e7268
   S:218E7300 E28D0010  ADD      r0,sp,#0x10
   S:218E7304 FA0621E3  BLX      _ZNSsC1EPKcRKSaIcE       <0x21a6fa98>
   S:218E7308 E1A0B000  MOV      r11,r0
   S:218E730C E1A0200A  MOV      r2,r10
   S:218E7310 E1A01000  MOV      r1,r0
   S:218E7314 E28D0014  ADD      r0,sp,#0x14
   S:218E7318 EB05C35F  BL       fapi_error::fapi_error   <0x21a5809c>
   S:218E731C E3A00008  MOV      r0,#8
   S:218E7320 FA056C58  BLX      __cxa_allocate_exception <0x21a42488>
   S:218E7324 E58D0008  STR      r0,[sp,#8]
   S:218E7328 E28D1014  ADD      r1,sp,#0x14
   S:218E732C EB05C340  BL       _ZN10fapi_errorC1ERKS_   <0x21a58034>
   S:218E7330 E58D0008  STR      r0,[sp,#8]
   S:218E7334 E28D0014  ADD      r0,sp,#0x14
   S:218E7338 EB05C36E  BL       _ZN10fapi_errorD1Ev      <0x21a580f8>
   S:218E733C E51F2F98  LDR      r2,0x218e63ac            <OSD\#1126>
   S:218E7340 E51F1F98  LDR      r1,0x218e63b0            <OSD\#1126>
   S:218E7344 E59D0008  LDR      r0,[sp,#8]
   S:218E7348 FB056D05  BLX      __cxa_throw              <0x21a42766>

Cela ne semble pas si effrayant, et aucune surcharge n'est ajoutée à l'intérieur des blocs ou des fonctions {} si l'exception n'est pas levée.

3
piotr

En plus de tous les commentaires, je vous proposerais de lire Rapport technique sur les performances C++ qui aborde spécifiquement les sujets qui vous intéressent: utiliser C++ en embarqué (y compris les systèmes durs en temps réel); comment le traitement des exceptions est généralement mis en œuvre et quels frais généraux il comporte; frais généraux de l'allocation gratuite de magasins.

Le rapport est vraiment bon, tout comme il démystifie de nombreuses queues populaires sur les performances C++.

3
Alexander Poluektov

Le plus gros problème avec STL dans les systèmes embarqués est le problème d'allocation de mémoire (qui, comme vous l'avez dit, pose beaucoup de problèmes).

Je rechercherais sérieusement la création de votre propre gestion de mémoire, construite en remplaçant les opérateurs new/delete. Je suis à peu près sûr qu'avec un peu de temps, cela peut être fait, et cela en vaut presque certainement la peine.

Quant à la question des exceptions, je n'irais pas là-bas. Les exceptions sont un sérieux ralentissement de votre code, car elles provoquent chaque bloc ({ }) pour avoir du code avant et après, permettant la capture de l'exception et la destruction de tout objet contenu à l'intérieur. Je n'ai pas de données fiables à ce sujet, mais chaque fois que j'ai vu ce problème se produire, j'ai vu des preuves accablantes d'un ralentissement massif provoqué par l'utilisation d'exceptions.

Modifier:
Puisque beaucoup de gens ont écrit des commentaires indiquant que la gestion des exceptions est pas plus lente, j'ai pensé ajouter cette petite note (merci pour les personnes qui ont écrit cela dans les commentaires, je l'ai pensé serait bon de l'ajouter ici).

La raison pour laquelle la gestion des exceptions ralentit votre code est que le compilateur doit s'assurer que chaque bloc ({}), de l'endroit où une exception est levée à l'endroit où elle est traitée, doit désallouer tous les objets qu'elle contient. C'est du code qui est ajouté à chaque bloc, que quelqu'un lève ou non une exception (car le compilateur ne peut pas dire au moment de la compilation si ce bloc fera partie d'une "chaîne" d'exceptions).

Bien sûr, cela pourrait être une ancienne façon de faire les choses qui est devenue beaucoup plus rapide dans les nouveaux compilateurs (je ne suis pas exactement à jour sur les optimisations du compilateur C++). La meilleure façon de le savoir est simplement d'exécuter un exemple de code, avec des exceptions activées et désactivées (et qui inclut quelques fonctions imbriquées), et de chronométrer la différence.

1
Edan Maor

Sur notre projet de scanner intégré, nous développions une carte avec processeur ARM7 et STL n'a posé aucun problème. Les détails du projet sont certainement importants, car l'allocation dynamique de la mémoire peut ne pas être un problème pour de nombreuses cartes et types de projets disponibles aujourd'hui.

1
mEbert