Suis-je optimisé prématurément?

9

Je suis actuellement au stade de la conception d'une architecture basée sur des composants en C ++.

Ma conception actuelle comprend l'utilisation de fonctionnalités telles que:

  • std::vectors de std::shared_ptrs pour maintenir les composants
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

Les composants représenteront les données et la logique de divers éléments nécessaires dans un logiciel de type jeu, tels que les graphiques, la physique, l'IA, l'audio, etc.

J'ai lu partout que les erreurs de cache sont néfastes sur les performances, j'ai donc effectué quelques tests, ce qui m'a amené à croire qu'en effet, cela pouvait ralentir une application.

Je n'ai pas pu tester les fonctionnalités linguistiques susmentionnées, mais il est dit dans de nombreux endroits que celles-ci ont tendance à coûter cher et doivent être évitées si possible.

Étant donné que je suis au stade de la conception de l'architecture, et ceux-ci seront inclus dans le cœur de la conception, devrais-je essayer de trouver des moyens de les éviter maintenant, car il sera très difficile de le changer plus tard s'il y a des performances problèmes?

Ou je suis juste pris à faire une optimisation prématurée?

Vaillancourt
la source
3
Je serais très réticent à m'installer sur une conception qui rendait très difficile de changer plus tard, quels que soient les problèmes de performances. Évitez cela si vous le pouvez. Il existe de nombreux modèles à la fois flexibles et rapides.
candied_orange
1
Sans même connaître les détails, la réponse à cette question est presque toujours un "OUI !!" retentissant.
Mawg dit réintégrer Monica le
2
@Mawg "... Pourtant, nous ne devons pas laisser passer nos opportunités dans ces 3% critiques." Puisque c'est le cœur de la conception, comment pourrais-je savoir si je travaille sur ces 3%?
Vaillancourt
1
Un excellent point, Alexandre (+1), et, oui, je sais que la dernière moitié de la citation, qui n'est presque jamais mentionnée :-) Mais, pour revenir à mon commentaire avant cela (qui se reflète dans la réponse acceptée) , the answer to this question is almost always a resounding "YES !!". Je pense toujours qu'il vaut mieux le faire fonctionner d'abord et l'optimiser plus tard, mais YMMV, tout le monde a son avis, tous valides, et seul l'OP peut vraiment répondre à sa propre question - subjective -.
Mawg dit réintégrer Monica le
1
@AlexandreVaillancourt Continuez à lire l'article de Knuth (PDF, la citation vient du côté droit de la page intitulée 268, page 8 dans un lecteur PDF). "... il sera sage d'examiner attentivement le code critique; mais seulement après que ce code aura été identifié. C'est souvent une erreur de porter un jugement a priori sur les parties d'un programme qui sont vraiment critiques, car l'expérience universelle de les programmeurs qui ont utilisé des outils de mesure est que leurs suppositions intuitives échouent. " (c'est lui qui souligne)
8bittree

Réponses:

26

Sans rien lire d'autre que le titre: Oui.

Après avoir lu le texte: Oui. Bien qu'il soit vrai que les cartes et les pointeurs partagés, etc. ne fonctionnent pas bien dans le cache, vous constaterez très certainement que ce pour quoi vous voulez les utiliser - pour autant que je sache - n'est pas le goulot d'étranglement et ne sera pas conservé ou utiliser efficacement le cache quelle que soit la structure des données.

Écrivez le logiciel en évitant les erreurs les plus stupides, puis testez, puis trouvez les goulots d'étranglement, puis optimisez!

Fwiw: https://xkcd.com/1691/

steffen
la source
3
D'accord. Faites-le fonctionner correctement en premier, car la vitesse à laquelle il ne fonctionne pas n'aura pas d'importance. Et rappelez-vous toujours que les optimisations les plus efficaces n'impliquent pas de modifier le code, elles impliquent de trouver un algorithme différent et plus efficace.
Todd Knarr du
10
Je voudrais souligner que la première ligne n'est pas vraie parce que l'optimisation est toujours prématurée, mais plutôt parce que l'optimisation n'est pas seulement prématurée si vous savez que vous en avez besoin, auquel cas vous ne vous poserez pas de questions à ce sujet. Ainsi, la première ligne n'est vraie que parce que le fait même que vous vous posiez la question de savoir si l'optimisation est prématurée signifie que vous n'êtes pas sûr d'avoir besoin d'une optimisation, ce qui, par définition, la rend prématurée. Phew.
Jörg W Mittag
@ JörgWMittag: d'accord.
steffen
3

Je ne connais pas C ++, mais en général cela dépend.

Vous n'avez pas besoin d'optimiser prématurément les algorithmes isolés où vous pouvez facilement les optimiser quand il s'agit de cela.

Cependant, vous devez obtenir la conception globale de l'application pour atteindre les indicateurs de performance clés souhaités.

Par exemple, si vous devez concevoir une application pour répondre à des millions de demandes par seconde, vous devez penser à l'évolutivité de l'application lors de sa conception, plutôt que de faire fonctionner l'application.

Pélican volant à basse altitude
la source
3

Si vous devez demander, alors oui. L'optimisation prématurée signifie l'optimisation avant de vous assurer qu'il existe un problème de performances important.

JacquesB
la source
1

ECS? Je vais en fait suggérer qu'il n'est peut-être pas prématuré, si c'est le cas, de réfléchir beaucoup au côté orienté données de la conception et de comparer les différents représentants, car cela pourrait avoir un impact sur vos conceptions d'interface , et ce dernier est très coûteux à modifier tard dans la le jeu. De plus, ECS exige juste beaucoup de travail et de réflexion en amont et je pense que cela vaut la peine d'utiliser une partie de ce temps pour vous assurer que cela ne vous causera pas de problèmes de performances au niveau de la conception plus loin étant donné la façon dont cela va être au cœur de votre tout le moteur flippant. Cette partie me regarde:

unordered_map<string,[yada]>

Même avec de petites optimisations de chaînes, vous avez un conteneur de taille variable (chaînes) à l'intérieur d'un autre conteneur de taille variable (unordered_maps). En fait, les petites optimisations de chaînes pourraient être aussi nuisibles qu'utiles dans ce cas si votre table est très clairsemée, car l'optimisation de petites chaînes impliquerait que chaque index inutilisé de la table de hachage utilisera toujours plus de mémoire pour l'optimisation SS ( sizeof(string)serait être beaucoup plus grande) au point où la surcharge de mémoire totale de votre table de hachage peut coûter plus cher que tout ce que vous y stockez, en particulier si c'est un composant simple comme un composant de position, en plus d'engendrer plus de ratés de cache avec l'énorme foulée pour passer d'une entrée de la table de hachage à la suivante.

Je suppose que la chaîne est une sorte de clé, comme un ID de composant. Si c'est le cas, cela rend déjà les choses considérablement moins chères:

unordered_map<int,[yada]>

... si vous voulez avoir les avantages d'avoir des noms conviviaux que les scripteurs peuvent utiliser, par exemple, alors les chaînes internes peuvent vous donner le meilleur des deux mondes ici.

Cela dit, si vous pouvez mapper la chaîne à une plage raisonnablement faible d'indices densément utilisés, vous pourrez peut-être le faire:

vector<[yada]> // the index and key become one and the same

La raison pour laquelle je ne considère pas cela prématuré est que, encore une fois, cela pourrait avoir un impact sur la conception de votre interface. Le but du DOD ne devrait pas être d'essayer de proposer les représentations de données les plus efficaces imaginables en une seule fois IMO (qui devraient généralement être réalisées de manière itérative si nécessaire), mais de les penser suffisamment pour concevoir des interfaces sur le dessus pour travailler avec cela. des données qui vous laissent suffisamment de marge de manœuvre pour profiler et optimiser sans modifier la conception en cascade.

À titre d'exemple naïf, un logiciel de traitement vidéo qui couple tout son code à cela:

// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels, 
// 16-bit channels, 32-bit channels, grayscale, monochrome, 
// etc. pixels.
class IPixel
{
public:
    virtual ~IPixel() {}
    ...
};

Ne va pas loin sans une réécriture potentiellement épique, car l'idée d'abstraire au niveau d'un seul pixel est déjà extrêmement inefficace ( vptrelle - même coûterait souvent plus de mémoire que le pixel entier) par rapport à l'abstrait au niveau de l' image (ce qui représentent souvent des millions de pixels). Donc, réfléchissez suffisamment à vos représentations de données à l'avance pour ne pas avoir à faire face à un tel scénario de cauchemar, et idéalement pas plus, mais ici, je pense que cela vaut la peine de penser à ce genre de choses dès le départ car vous ne voulez pas construire un moteur complexe autour de votre ECS et constatez que l'ECS lui-même est le goulot d'étranglement d'une manière qui vous oblige à changer les choses au niveau de la conception.

En ce qui concerne les échecs de cache ECS, à mon avis, les développeurs s'efforcent souvent trop de rendre leur cache ECS convivial. Cela commence à céder trop peu pour que l'argent essaie d'accéder à tous vos composants de manière parfaitement contiguë, et impliquera souvent de copier et de mélanger les données partout. Il est généralement suffisant, par exemple, de simplement trier les index des composants avant de les accéder afin que vous y accédiez de manière à ne pas charger au moins une région de mémoire dans une ligne de cache, uniquement pour l'expulser, puis charger tout à nouveau dans la même boucle juste pour accéder à une partie différente de la même ligne de cache. Et un ECS n'a pas à fournir une efficacité incroyable à tous les niveaux. Ce n'est pas comme si un système d'entrée en bénéficiait autant qu'un système de physique ou de rendu, je recommande donc de viser «bon» efficacité à tous les niveaux et "excellente" seulement là où vous en avez vraiment besoin. Cela dit, l'utilisation deunordered_mapet stringvoici assez facile à éviter.


la source