Optimiere ich vorzeitig?

9

Ich befinde mich derzeit in der Entwurfsphase einer komponentenbasierten Architektur in C ++.

Mein aktuelles Design beinhaltet die Verwendung von Funktionen wie:

  • std::vectors von std::shared_ptrs, um die Komponenten zu halten
  • std::dynamic_pointer_cast
  • std::unordered_map<std::string,[yada]>

Komponenten repräsentieren Daten und Logik verschiedener Elemente, die in einer spielerischen Software benötigt werden, wie z. B. Grafik, Physik, KI, Audio usw.

Ich habe überall gelesen, dass Cache-Fehler die Leistung beeinträchtigen, daher habe ich einige Tests durchgeführt, die mich zu der Annahme veranlassten, dass dies tatsächlich eine Anwendung verlangsamen kann.

Ich war nicht in der Lage, die oben genannten Sprachfunktionen zu testen, aber es wird an vielen Stellen gesagt, dass diese tendenziell viel kosten und nach Möglichkeit vermieden werden sollten.

Da ich mich in der Entwurfsphase der Architektur befinde und diese im Kern des Entwurfs enthalten sein werden, sollte ich versuchen, Wege zu finden, um sie jetzt zu vermeiden, da es sehr schwierig sein wird, sie später zu ändern, wenn Leistung vorhanden ist Probleme?

Oder bin ich nur in der vorzeitigen Optimierung gefangen?

Vaillancourt
quelle
3
Ich würde mich sehr ungern für ein Design entscheiden, das es sehr schwierig macht, es später zu ändern, unabhängig von den Leistungsproblemen. Vermeiden Sie das, wenn Sie können. Es gibt viele Designs, die sowohl flexibel als auch schnell sind.
candied_orange
1
Ohne die Details zu kennen, ist die Antwort auf diese Frage fast immer ein klares "JA !!".
Mawg sagt, Monica
2
@Mawg "... Dennoch sollten wir unsere Chancen bei diesen kritischen 3% nicht verpassen." Da dies der Kern des Designs ist, wie kann ich wissen, ob ich an diesen 3% arbeite?
Vaillancourt
1
Ein hervorragender Punkt, Alexandre (+1), und ja, ich weiß, dass die letzte Hälfte des Zitats fast nie erwähnt wird :-) Aber um auf meinen vorherigen Kommentar zurückzukommen (was sich in der akzeptierten Antwort widerspiegelt) , the answer to this question is almost always a resounding "YES !!". Ich bin immer noch der Meinung, dass es besser ist, es zuerst zum Laufen zu bringen und später zu optimieren, aber YMMV, jeder hat seine Meinung, die alle gültig sind, und nur das OP kann seine eigene - subjektive - Frage wirklich beantworten.
Mawg sagt, Monica
1
@AlexandreVaillancourt Lesen Sie weiter Knuths Artikel (PDF, Zitat stammt von der rechten Seite der Seite mit der Bezeichnung 268, Seite 8 in einem PDF-Reader). "... er wird klug sein, den kritischen Code sorgfältig zu betrachten; aber erst nachdem dieser Code identifiziert wurde. Es ist oft ein Fehler, a priori zu beurteilen, welche Teile eines Programms wirklich kritisch sind, da die universelle Erfahrung von Programmierer, die Messwerkzeuge verwendet haben, haben festgestellt, dass ihre intuitiven Vermutungen fehlschlagen. " (Hervorhebung seiner)
8bittree

Antworten:

26

Ohne etwas anderes als den Titel zu lesen: Ja.

Nach dem Lesen des Textes: Ja. Obwohl es ist wahr , dass Karten und gemeinsame Zeiger usw. nicht durchführen gut Cache-weise, werden Sie sicherlich feststellen , dass , was Sie sie für verwenden möchten - soweit ich verstehen - nicht der Engpass ist und nicht in gehalten werden oder Verwenden Sie den Cache unabhängig von der Datenstruktur effizient.

Schreiben Sie die Software, um die dümmsten Fehler zu vermeiden, testen Sie sie, finden Sie die Engpässe und optimieren Sie sie!

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

steffen
quelle
3
Einverstanden. Stellen Sie sicher, dass es zuerst richtig funktioniert, da es keine Rolle spielt, wie schnell es nicht funktioniert. Und denken Sie immer daran, dass die effektivsten Optimierungen nicht das Optimieren des Codes beinhalten, sondern das Finden eines anderen, effizienteren Algorithmus.
Todd Knarr
10
Ich möchte darauf hinweisen, dass die erste Zeile nicht wahr ist, weil die Optimierung immer verfrüht ist, sondern weil die Optimierung nur dann nicht verfrüht ist, wenn Sie wissen, dass Sie sie benötigen. In diesem Fall würden Sie nicht danach fragen. Die erste Zeile ist also nur wahr, weil die Tatsache, dass Sie eine Frage stellen, ob die Optimierung verfrüht ist oder nicht, bedeutet, dass Sie nicht sicher sind, ob Sie eine Optimierung benötigen, was sie per Definition verfrüht. Puh.
Jörg W Mittag
@ JörgWMittag: vereinbart.
steffen
3

Ich bin nicht mit C ++ vertraut, aber im Allgemeinen kommt es darauf an.

Sie müssen die isolierten Algorithmen nicht vorzeitig optimieren, da Sie diesbezüglich problemlos optimieren können.

Sie müssen jedoch ein Gesamtdesign der Anwendung erstellen, um die gewünschten Leistungsindikatoren zu erzielen.

Wenn Sie beispielsweise eine Anwendung entwerfen müssen, um Millionen von Anforderungen pro Sekunde zu erfüllen, müssen Sie beim Entwerfen über die Skalierbarkeit der Anwendung nachdenken, anstatt die Anwendung zum Laufen zu bringen.

Niedrig fliegender Pelikan
quelle
3

Wenn Sie fragen müssen, dann ja. Vorzeitige Optimierung bedeutet Optimierung, bevor Sie sicher sind , dass ein erhebliches Leistungsproblem vorliegt.

JacquesB
quelle
1

ECS? Ich schlage tatsächlich vor, dass es möglicherweise nicht verfrüht ist, wenn Sie viel über die datenorientierte Seite des Designs nachdenken und verschiedene Wiederholungen bewerten, da dies Auswirkungen auf Ihre Schnittstellendesigns haben kann. Letztere Änderungen sind sehr kostspielig das Spiel. Außerdem erfordert ECS nur eine Menge Arbeit und Überlegungen im Voraus, und ich denke, es lohnt sich, einen Teil dieser Zeit zu nutzen, um sicherzustellen, dass Sie später keine Leistungsprobleme mehr auf Designebene haben, wenn man bedenkt, wie wichtig es für Sie sein wird ganze verdammte Maschine. Dieser Teil starrt mich an:

unordered_map<string,[yada]>

Selbst bei kleinen String-Optimierungen haben Sie einen Container mit variabler Größe (Strings) in einem anderen Container mit variabler Größe (unordered_maps). Tatsächlich könnten die Optimierungen für kleine Zeichenfolgen in diesem Fall genauso schädlich wie hilfreich sein, wenn Ihre Tabelle sehr spärlich ist, da die Optimierung für kleine Zeichenfolgen bedeuten würde, dass jeder nicht verwendete Index der Hash-Tabelle immer noch mehr Speicher für die SS-Optimierung benötigt ( sizeof(string)würde viel größer sein) bis zu dem Punkt, an dem der gesamte Speicheraufwand Ihrer Hash-Tabelle möglicherweise mehr kostet als alles, was Sie darin speichern, insbesondere wenn es sich um eine einfache Komponente wie eine Positionskomponente handelt, zusätzlich zu mehr Cache-Fehlern mit dem großen Schritt um von einem Eintrag in der Hash-Tabelle zum nächsten zu gelangen.

Ich gehe davon aus, dass die Zeichenfolge eine Art Schlüssel ist, wie eine Komponenten-ID. Wenn ja, macht dies die Dinge bereits dramatisch billiger:

unordered_map<int,[yada]>

... wenn Sie die Vorteile von benutzerfreundlichen Namen haben möchten, die Scripter verwenden können, z. B. können internierte Zeichenfolgen Ihnen hier das Beste aus beiden Welten bieten.

Wenn Sie die Zeichenfolge jedoch einem relativ niedrigen Bereich dicht verwendeter Indizes zuordnen können, können Sie möglicherweise Folgendes tun:

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

Der Grund, warum ich dies nicht für verfrüht halte, ist, dass es sich wiederum auf Ihre Schnittstellendesigns auswirken könnte. Der Sinn von DOD sollte nicht darin bestehen, zu versuchen, die effizientesten Datendarstellungen zu erstellen, die in einer IMO auf einmal vorstellbar sind (die im Allgemeinen bei Bedarf iterativ erreicht werden sollten), sondern sie ausreichend zu überlegen, um darüber hinaus Schnittstellen zu entwerfen, um damit zu arbeiten Daten, die Ihnen genügend Freiraum lassen, um Profile zu erstellen und zu optimieren, ohne Designänderungen zu kaskadieren.

Als naives Beispiel eine Videoverarbeitungssoftware, die den gesamten Code damit koppelt:

// 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() {}
    ...
};

Ohne ein möglicherweise episches Umschreiben wird es nicht weit kommen, da die Idee des Abstrahierens auf Einzelpixelebene bereits äußerst ineffizient ist (das vptrselbst kostet oft mehr Speicher als das gesamte Pixel) als das Abstrahieren auf Bildebene (was auch der Fall sein wird) stellen oft Millionen von Pixeln dar). Denken Sie also im Voraus genug über Ihre Datendarstellungen nach, damit Sie sich keinem solchen Alptraumszenario stellen müssen, und im Idealfall nicht mehr, aber ich denke, es lohnt sich, im Voraus über dieses Zeug nachzudenken, da Sie keine erstellen möchten komplizierte Engine um Ihr ECS und stellen Sie fest, dass das ECS selbst der Engpass in einer Weise ist, die es erforderlich macht, dass Sie die Dinge auf Designebene ändern.

Was ECS-Cache-Fehler betrifft, versuchen Entwickler meiner Meinung nach oft zu sehr, ihre ECS-Cache-freundlich zu gestalten. Es gibt zu wenig Knall für das Geld, um zu versuchen, auf alle Ihre Komponenten auf perfekt zusammenhängende Weise zuzugreifen, und impliziert oft das Kopieren und Mischen von Daten überall. Es ist normalerweise gut genug, vor dem Zugriff beispielsweise nur Radix-Sortierkomponentenindizes zu erstellen, damit Sie auf sie so zugreifen, dass Sie zumindest keinen Speicherbereich in eine Cache-Zeile laden, nur um ihn zu entfernen und dann zu laden alles noch einmal in derselben Schleife, nur um auf einen anderen Teil derselben Cache-Zeile zuzugreifen. Und ein ECS muss nicht auf ganzer Linie eine erstaunliche Effizienz bieten. Es ist nicht so, dass ein Eingabesystem davon profitiert, sondern ein Physik- oder Rendering-System. Ich empfehle daher, "gut" anzustreben. Effizienz auf ganzer Linie und "exzellent" nur dort, wo Sie es wirklich brauchen. Das heißt, Verwendung vonunordered_mapund stringhier sind leicht genug zu vermeiden.


quelle