C++ Code Optimierung
-
Du meinst den Profiler DevPartner Performance Analysis Community Edition?
-
hat vs nicht 'nen eingebauten profiler? der alte vs6 hatte das noch.
aber mal zum thema: wenn man ein array aus function pointern 'const' oder 'static const' definiert, alle elemente initialisiert und geschwindigkeitsoptimierung einschaltet, dann kann es sein, dass ein compiler sehr performanten code draus macht, der sich in puncto speed nicht von der switch/case-variante unterscheidet. man sollte sich auf jeden fall mal den erzeugten asm-code anschauen...
:xmas2:
-
PiCiJi schrieb:
Du meinst den Profiler DevPartner Performance Analysis Community Edition?
Doll, das Ding kostet n paar tausend Euro^^
Edit: Und ich hab auch noch nach nem Download-Link gesucht...
-
Profiler war beim VC6 dabei, beim 2003er und 2005er ist der leider nichtmehr dabei. Vom 2005er gibts die Team Edition oder wie die genau heisst, da ist wieder ein Profiler mit dabei. Kostet aber auch empfindlich mehr als die Pro Version...
@PiCiJi:
Du solltest gucken dass du sowenig wie möglich "ifs" hast deren Ausgang öfters wechselt. Ein gutes Beispiel ist das if in execute_cycle. Genauso sind aber alle "switch-case" in den einzelnen "opcode-Funktionen" betroffen, die werden im generierten Code genauso bedingte Sprünge werden, und die tun weh. Eine Möglichkeit einiges davon zu vermeiden wäre, jedem einzelnen Cyklus eines Opcodes eine eigene Funktion zu verpassen, und diese dann über einen Function Pointer aufzurufen. Das sollte nochmal einiges bringen. z.B. so:class Cpu { public: // ... unsigned char read_pc() { ... } void fetch_instruction() { opcode = read_pc(); cycle_pos = 1; cycle_function = first_cycle_function_for_opcode[opcode]; } void execute_cycle() { (this->*cycle_function)(); } void op_and_absolute_c1() { aa_l = read_pc(); cycle_pos = 2; cycle_function = &Cpu::op_and_absolute_c2; } void op_and_absolute_c2() { aa_h = read_pc(); cycle_pos = 3; cycle_function = &Cpu::op_and_absolute_c3; } void op_and_absolute_c3() { if(reg_p_m) { check_for_interrupts(); data_l = read_aa(DBR_, aa_w); logic_and_b(); cycle_pos = 0; cycle_function = &Cpu::fetch_instruction; } else { data_l = read_aa(DBR_, aa_w); cycle_pos = 4; cycle_function = &Cpu::op_and_absolute_c4; } } void op_and_absolute_c4() { check_for_interrupts(); data_h = read_aa(DBR_, aa_w + 1); logic_and_w(); cycle_pos = 0; cycle_function = &Cpu::fetch_instruction; } // ... typedef void (Cpu::*CycleFunctionPtr)(); CycleFunctionPtr cycle_function; static CycleFunctionPtr first_cycle_function_for_opcode[256]; };Der Sprung über die Pointertabelle in execute_cycle wird durch einen Sprung über einen einzigen Pointer ersetzt, was schonmal gut ist, da das Sprungziel dadurch schon früher bekannt ist. execute_cycle sollte dabei natürlich auf jeden Fall inline gemacht werden, denn 2 Sprünge so knapp hintereinander kann die CPU auch nicht so gut verdauen wie wenn vor jedem Sprung noch ein paar Zyklen vergehen.
Weiters entfallen die ganzen "switch(cycle_pos)", was wiederum einiges bringen sollte.Eventuell (ich kenne ja den restlichen Code nicht) kannst du dann sogar ganz auf die "cycle_pos" Variable verzichten.
Und es wäre noch eine Überlegung Wert die ganzen "opcode-Funtionen" static zu machen, und den this Pointer immer explizit mitzugegen -- ob das was bringt kommt allerdings darauf an wie gut (und schnell) dein Compiler mit Member-Function-Pointern umgehen kann (manche Compiler sind da ziemlich doof).
-
Badestrand schrieb:
PiCiJi schrieb:
Du meinst den Profiler DevPartner Performance Analysis Community Edition?
Doll, das Ding kostet n paar tausend Euro^^
Edit: Und ich hab auch noch nach nem Download-Link gesucht...
Die community edition kostet doch nix -> klick mich
-
@hustbaer: danke für die Idee. Das sieht vielversprechend aus. Ich wandle alle Maschinenbefehle am WE derart ab. Der Methodenzeiger cycle_function wird also mit der Methode fetch_instruction initialisiert und dann steht am Ende jeder Zyklus Methode der nächste Zyklus durch Neubelegung des Methodenzeigers fest. Wird ein neuer opcode gefetcht, steht in einem 256 Elemente grossen Array jweils ein Zeiger auf die erste Zyklus Methode eines opcodes.
Auf die cycle_pos Variable kann ich dann verzichten, da ich diese nur noch in der Methode benötige, welche am Ende execute_cycle(); aufruft und neben einem DMA Transfer die Aufgabe hat am Ende eines abgearbeiteten Maschinenbefehls einen eventuell reingeschwebten Interrupt zu verarbeiten.
void Cpu::run() { if(state_dma) {control_dma(); return;} if(cycle_pos == 0) { if(res_pending) { res_pending = false; perform_hardware_interrupt(true); sys.reset(); return; } else if(nmi_pending) { nmi_pending = false; aa_w = (mode_e) ? 0xfffa : 0xffea; perform_hardware_interrupt(); return; } else if(irq_pending) { irq_pending = false; aa_w = (mode_e) ? 0xfffe : 0xffee; perform_hardware_interrupt(); return; } } execute_cycle(); }ich könnte "if(cycle_pos == 0)" ersetzen durch " if(cycle_function == &Cpu::fetch_instruction). Ok ich verwende execute_cycle auf jeden Fall inline. Obwohl das die Instrument Guided Optimization sicher selber erkennt, egal mit welchem Spiel ich evaluiere.
ok ich versuche mir auch den Profiler ziehen.
Wenn das was bringt, könnte ich deine Optimierung auch auf den 2. Instruktion Prozessor anwenden.
-
Da das eine GROSSE Änderung wird zieh dir auf jeden Fall vorher eine Kopie, bzw. falls du ein gutes Source-Control-System hast mach sie in einem Branch. Falls es nixe bringt oder sogar schlechter wird haste immer noch das Original. Obwohl ich sehr bezweifle dass es schlechter wird -- sicher sein kann man ja nie.
-
so groß wird die Änderung nicht. Ich habe zur besseren Fehlerkorrektur der Maschinenbefehle diese komprimiert in einer Text Datei zusammengefasst. Befehle mit gleichen Zyklen aber unterschiedlicher Logik sind dort zusammengefasst. Die Logik wird in der Regel innerhalb eines Zykluses verarbeitet und die Zyklen haben die Aufgabe Speicherstellen zu lesen, diese in Register zu laden usw. Ein C++ Code wandelt diese 26 kb grosse Text Datei dann in eine 114 kb grosse C++ Datei um und natürlich die Initialisierung des Methoden Zeiger Array. Ich passe also nur diesen Code an.
-
Hehe. Auch nicht doof

-
ok ich habe alles umgewandelt und execute_cycle geinlined. Leider gibt es keinen Unterschied zu früher weder im debug noch im release mode. Je nach Spiel hab ich zwischen 100 und 120 fps auf einem 3,5 GHz Amd. 60 fps sind Originalgeschwindigkeit.
Trotzdem danke. Wie meintest du das, die Methoden static machen und den this Zeiger explizit übergeben? Ich denke static Methoden sind nicht Objekt gebunden und somit hat der this Zeiger keine Bedeutung? Kann man denn eine ganze Klasse static setzen. Wie gesagt ich hab in der Regel immer nur eine Instanz pro Klasse. Ich frage mich wirklich, ob es was bringt das Klassenkonstrukt aufzulösen.
-
PiCiJi schrieb:
ok ich habe alles umgewandelt und execute_cycle geinlined. Leider gibt es keinen Unterschied zu früher weder im debug noch im release mode. Je nach Spiel hab ich zwischen 100 und 120 fps auf einem 3,5 GHz Amd. 60 fps sind Originalgeschwindigkeit.
Trotzdem danke. Wie meintest du das, die Methoden static machen und den this Zeiger explizit übergeben? Ich denke static Methoden sind nicht Objekt gebunden und somit hat der this Zeiger keine Bedeutung? Kann man denn eine ganze Klasse static setzen. Wie gesagt ich hab in der Regel immer nur eine Instanz pro Klasse. Ich frage mich wirklich, ob es was bringt das Klassenkonstrukt aufzulösen.Gemeint ist
void (*op_table[256])(Cpu*);statt
void (Cpu::*op_table[256])();und dann an Stelle von this die Benutzung des übergebenen Zeigers.
-
Ja, genau das ist gemeint.
Schade dass es nix gebracht hat, wunder mich doch etwas. Aber vielleicht geht die meiste Zeit ja ganz woanders drauf...?
Was sagt denn der VTune?Und... was ist das überhaupt für ein Projekt?
Emulation, Spiele, 4 CPUs ... klingt irgendwie interessant.
-
ok ich habe jetzt eine ganze Klasse meines Projektes statisch gemacht. Das bringt ca. 2 fps mehr. Als nächstes werde ich die Cpu Klasse statisch machen. Mal schauen, ob es noch mehr bringt. Ziel ist es dann alle Klassen statisch zu machen und keine Objekte mehr anzulegen. Ausnahme ist die Config Klasse.
Ich bastle seit letztem Januar 06 an einem Snes Emulator.
So ich habe jetzt compuserve performance analyser verwendet. Ok das Programm gibt mir eine ganze Reihe infos. Wird ne Weile dauern alles auszuwerten. Am meisten ist er damit beschäftigt auf Interrupts zu überprüfen. Jeder Maschinenbefehlzyklus verbraucht 6,8 oder 12 Clockzyklen. Somit wird nach jeden verbrauchten 6,8 oder 12 Clockzyklen überprüft ob ein Interrupt verarbeitet werden muss.
-
Eu. Also irgendwas musst du da wohl falsch machen, SNES Emulatoren gibts einige die auf sehr bescheidener Hardware bereits gut laufen

-
hustbaer schrieb:
Eu. Also irgendwas musst du da wohl falsch machen
ja, er benutzt c++ mit klassen, 'this' und dem ganzen schnickschnack

hier gibts'n paar, einige mit source codes:
--> http://www.zophar.net/snes.html
-
na das kommt auf die Genauigkeit an. Die meisten Emulatoren arbeiten einen ganzen Maschinenbefehl am Stück ab. Das geht deutlich schneller, als wenn immer nur ein Zyklus abgearbeitet wird und dann synchronisiert wird. Vor ein paar Jahren war ein Zyklen genauer Emulator nicht wirklich interressant, da zu langsam. Das sieht jetzt anders aus. Zyklengenaue Emulation ist besonders für ältere Systeme mit geringen Cpu Geschwindigkeiten sinnvoll. Individualanpassungen für einzelne Spiele werden somit verschwindend gering. Die Fehlerwahrscheinlichkeit ist geringer, schließlich kann man nicht alle Spiele, inklus. allen Spielsituationen testen.
-
Ok, klar. Obwohl... gerade das gucken ob ein Interrupt anliegt... ob das wirklich Zyklengenau gehen muss?
Als Programmierer kann man sich sowieso nicht gut drauf verlassen dass dies oder jenes jetzt genau N Zyklen dauert und dann genau bei diesem oder jenem Zyklus der Interrupt kommt...
-
ich überprüfe nicht darauf, ob er exakt die position erreicht hat, sondern addiere die vergangenen Clockzyklen zum letzten Wert hinzu und überprüfe dann, ob was in diesem Zeitraum passiert ist. Aber ich habe noch ein paar Ideen. Jetzt mache ich die Cpu Klasse statisch.
edit: So ein Mist man kann keine statischen, anonymen unions verwenden. Das bedeutet hunderte von Variablen, die ich umbenennen muss. Der Aufwand ist mir zu gross für höchstwahrscheinlich keinen Geschwindigkeitsgewinn.