Hallo Besucher, der Thread wurde 7,5k mal aufgerufen und enthält 53 Antworten

letzter Beitrag von Mike am

Viele Sprünge usw - wie handhabt ihr das?

  • Anderes Beispiel: Du hast solchen Code geschrieben, und irgendwann später möchtest du den selben Code für ein anderes Projekt in einem EPROM verwenden. Jetzt musst du alles entsprechend anpassen.

    In diesem (sehr unwahrscheinlichen) Fall setze ich Kommentarzeichen vor die Definitionen der beiden Symbole "MODIFIED8" und "MODIFIED16" und schon gibt mir der Assembler eine Liste aller Stellen, die angepasst werden müssen. Das ist deutlich weniger Arbeit als von vornherein auf Selfmods zu verzichten.

    Das Eprom (Modul) dient doch nur als Datengeber /-träger. Das Programm läuft auch bei Modulen immer im C64 eigenen Speicher. Bei Spielekonsolen ist das ebenso.

    Wirf mal einen Blick auf die RAM-Ausstattung diverser modulbasierter Spielekonsolen.

  • Jetzt nicht das essentielle einfach aus Gehässigkeit verdrehen. Dass es auch irgendwelche RAM Bausteine zusätzlich zu den eigentlichen Rombausteinen in Modulen gibt (für Spielstandspeicherungen oder andere Dinge) ist schon klar. Alles ist möglich. Der Code stammt aber eben immernoch nur aus den Eprom- / Rombausteinen des Moduls.
    Der wird dann sonstwo in ein Ram hingeladen (manchmal auch entpackt) und erst da beginnt im Grunde das Programm.. .


    64KB Mega Drive, 128KB SNES dazu jeweils noch etwas Video- und Soundram. Weiß ich schon immer solange es das I-Net gibt.
    Nur da läuft das Programm ab. Zwischendurch wird immer 'was vom Modul in dieses Ram nachgeladen u. (anschließend) ggf. entpackt.
    Bekommt man nur nicht immer mit (bei Another World auf dem lahmen SNES aber schon, ein gutes Beispiel), weil's relativ schnell geht. Nie sind die 512KB bis 4MB eines Moduls aufeinmal aktiv.


    Manchmal macht das Bild + Ton auch bei ein paar Spielen vor einem Boss eine ca. 0,5 bis 1,0 sekündige Pause, weil in der Zeit etwas vom Modul (der upcoming Boss + Bossroutinen + ein neuer Tune) nachgeladen wird [Strider, Golden Axe, Ghouls 'n Ghosts fällt mir da z.B. ein]. ;) Oder mindestens etwas schon im Ram vorhandenes entpackt wird.

  • Eine kurze Frage: (ich bin C64 / 6510 programmiertechnisch kompletter Anfänger)
    Warum erspart man sich bei selbst modifizierenden Code so viel bei der 6510 Architektur?
    Ich finde das auch irgendwie komisch. Hab früher vieles auf einem embedded Z80 Board gemacht, da kam der Code direkt aus Flash/EPROM.
    Warum erspart man sich bei 6510 damit soviel Code? Hat das mit der begrenzten Register Zahl zu tun?

  • Das hat ja nix mit "Angewöhnen" zu tun. Auf einem System wie dem C64 ist das nunmal eine der simpelsten Methoden, etwas zu erreichen, was man ansonsten nur weitaus umständlicher zustande bekommt. Das hat nix mit anderen Systemen oder Prozessoren zu tun. Deinen Satz "aber jeder wie er meint" müsste eher lauten "auf jedem System das entsprechende Vorgehen/Werkzeug".

    Wenn sich ein Problem nicht anders lösen läßt (aus Performance- oder Speicher-Gründen), ist meiner Meinung nach alles erlaubt. Sollte dann eben gut dokumentiert und um Quelltext erklärt sein, was man da warum tut.


    Aber als genereller Programmierstil ist das absolut verpönt. Meine ersten 6502-Assemblerprogramme waren auch selbstmodifizierend. Dann habe ich das beruflich gemacht und musste mich komplett umstellen. Dank indirekter Adressierung gibt es normalerweise keine Notwendigkeit für selbstmodifizierenden Code. Und warum soll man ein Programm anders schreiben, nur weil es zufällig mal im RAM und nicht im ROM abläuft? Also besser gar nicht erst angewöhnen.


    Und dass selbstmodifizierender Code generell immer performanter und kleiner ist, das wage ich auch zu bezweifeln.

  • nochmal: ein c64 cartridge wird in den Speicher eingeblendet. Da wird nichts kopiert, sondern die Adresslogik (aka PLA) wählt das Eprom auf dem Modul statt des RAM an und der code läuft aus dem ROM.


    Beispiel VCS: 128 byte RAM, module bis 4kb Eprom, der Adressraum des Prozessors ist auf 8kb beschnitten. Da wird nichts kopiert.

  • Warum erspart man sich bei selbst modifizierenden Code so viel bei der 6510 Architektur?

    Für den Fall, daß Du das auf die Erwähnung meiner Linienziehroutine in Post #15 beziehst:


    die Routine ist unter anderem deswegen so schnell, weil der größte Teil der Adreßberechnungen eliminiert werden konnte, indem die zuerst berechnete Adresse fortgeschrieben wird. Desweiteren wird in einen in Spalten angeordneten Zeichensatz geplottet, damit geht der Zugriff einfach über (ZP),Y - und in Y steht bereits die Y-Koordinate.


    Für Steigungen <1 sieht nun 1/8 der Hauptschleife so aus:

    ... wobei die weiteren 7 Exemplare bei ORA die Argumente #$40, #$20, ... #$01 durchgehen. Für Steigungen >=1 gibt es eine zweite Hauptschleife, wo die Befehle leicht umgeordnet sind.


    So wie hier gezeigt kann die Hauptschleife die Y-Koordinate nur inkrementieren, und die Punkte auch nur setzen.


    In der Vorbereitungsroutine werden nun ggfs. alle INY durch DEY ersetzt, sowie ORA gegen EOR oder AND (wobei beim Wechsel zwischen EOR/ORA <-> AND das Argument auch noch ausgetauscht werden muß). Damit kann die Routine dann auch Linien löschen oder invertieren. Der Austausch geht recht fix und wird auch nur in der Hauptschleife (<1 oder >=1) gemacht, die gerade dran ist, und auch nur dann wenn es nötig ist.


    Ohne selbstmodifizierenden Code müßte die Routine alle Varianten "in echt" vorhalten, wäre also etwa 2{INY/DEY} x 3{ORA/EOR/AND} = 6x so groß (höchstens abzüglich des Codes der die Selbstmodifikation ausführt...)

  • Warum erspart man sich bei 6510 damit soviel Code? Hat das mit der begrenzten Register Zahl zu tun?

    Ob man wirklich viel spart, hängt vom Einzelfall ab. Bei der indirekten Adressierung steht der Zeiger in der Zero Page und er kann mit schnellen und kurzen 2-Byte-Befehlen modifiert werden. Das Programm steht normalerweise nicht in der Zero Page, also modifiert man das Programm mit teuren 3-Byte-Absolut-Befehlen. Ich glaube nicht, dass man da unterm Strich soviel spart.


    Die ganze Architektur des 6502 basiert auf der Zero Page und der indirekten Adressierung. Warum man das dann nicht benutzt, ist mir auch ein Rätsel. Normalerweise arbeitet man mit und nicht gegen die Architektur eines Prozessors. :D


    Ohne selbstmodifizierenden Code müßte die Routine alle Varianten "in echt" vorhalten, wäre also etwa 2{INY/DEY} x 3{ORA/EOR/AND} = 6x so groß (höchstens abzüglich des Codes der die Selbstmodifikation ausführt...)

    Wieviel Bytes sparst du absolut?


    Übrigens habe ich gar kein Problem mit Routinen, die vor dem Aufruf modifizert werden (z. B. wenn man Adressanpassungen vornimmt - das macht jeder Relocator beim Laden von Programmen). Das ist im Grunde gar nicht selbstmodifizierend, weil der Programmcode von einem anderen Programmcode modifiziert wird. Übel ist, wenn sich eine Routine während der Ausführung selber modifiziert.

  • ein sta,x und dann MSB selfmod ist nunmal schneller als
    ein sta (ZP),y


    Wenn jemand strikt nach Regelwerk XY coden will, kann er das ja gerne. Aber der C64 und die Szene leben davon, dass man Dinge ausreizt. Etwas als "das macht man aber nicht" zu titulieren empfinde ich als hoechst unkreativ :-)

  • Wieviel Bytes sparst du absolut?

    Ernsthaft - die Routine funktioniert so, wie sie gebaut ist und ich sehe keinen Anlaß, sie jetzt in eine Version ohne selbstmodifizierenden Code umzubauen damit ich dir diese Frage aufs Byte beantworten kann.


    Etwas feiner geschätzt brauchen die Vorbereitungsroutinen etwa 250 Byte, die ließen sich bei Wegfall des Codes, der die Selbstmodifikation ausführt, etwa um die Hälfte einkürzen. Die Tests, welcher der 6 Fälle jetzt gebraucht wird, müssen aber drin bleiben, damit die richtige "Kopie" der Hauptschleife angesprungen werden kann.


    Diese 550 Byte für die 2 Hauptschleifen (<1, >=1) gibt es also jetzt 6x => Die gesamte Routine ist dann also geschätzt 6x550+1/2*250 Byte groß ~= 3,4 KB. Nicht ganz so viel wie bei meiner groben Schätzung oben - aber es sind aber doch 2600 Bytes, die "verdunsten", wenn ich hier auf Selbstmodifikation verzichte.


    Wenn mir die Geschwindigkeit weitgehend egal ist, kommt ohnehin eine andere Routine zum Zug. Die schafft "nur" 4000 Punkte/Sekunde, ist aber auch nur 100 Byte groß (wobei die eigentliche Punktsetzroutine hier nicht drin ist, da sie schon in einem anderen Codeteil beigestellt wurde - das ist also nur der Bresenham selbst).


    ...


    Und natürlich handelt es sich hierbei um selbstmodifizierenden Code. Hier wird Code zur Laufzeit umgeändert. Da ist es egal, ob eine Routine sich selbst oder eine andere Routine ändert. Die CPU interessiert das auf dem hier nicht vorhandenden Abstraktionsgrad schon mal überhaupt nicht.


    Im Grunde hast Du schon selbstmodifizierenden Code, wenn Du nur ein Programm in den Speicher lädst. :D (Nicht lachen! Da muß ggfs. extra deswegen auf CPUs mit Cache derselbe gelöscht werden! Höchstselbst miterlebt, beim Sprung ARM auf StrongARM...) Gibt auch noch dynamischen Code, der anhand einer Schablone komplett neuen Code in den Speicher schreibt. Etc. pp.

  • ein sta,x und dann MSB selfmod ist nunmal schneller als
    ein sta (ZP),y

    Ja, aber korrekt steht da:


    STA abs,X
    STA (zp),Y


    Diese abs und zp Adressen müssen inkrementiert, dekrementiert oder sonst irgendwie verändert werden. Im ersten Fall muss man das mit 3-Byte Absolut-Adressen im Programmcode machen, im zweiten Fall macht man das mit 2-Byte Zero-page-Adressierung. Und dann ist eben die Frage, was unter'm Strich noch dabei rumkommt.


    Klar, der erst Fall ist schneller, solange man nur X erhöht. Aber kürzer ist es schonmal nicht.

  • @Mike und @detlef Danke für eure Erklärungen! Tut mir leid für das OFF Topic.
    Das Beispiel mit deiner Zeichenroutine finde ich sehr interessant. Das hat für mich einen Hauch von Lambda Funktion, aber eben auf Assembler Ebene. Aber würde sich das nicht auch durch einen Jump auf eine jeweilige konfigurierte Unterfunktion (und daher Adresse) realisieren lassen?
    Natürlich mit etwas Laufzeit Einbuße. Also eine Mini Unterfunktion.

  • ein sta,x und dann MSB selfmod ist nunmal schneller als
    ein sta (ZP),y


    Wenn jemand strikt nach Regelwerk XY coden will, kann er das ja gerne. Aber der C64 und die Szene leben davon, dass man Dinge ausreizt. Etwas als "das macht man aber nicht" zu titulieren empfinde ich als hoechst unkreativ :-)


    Dem stimme ich absolut zu, wenn es auf die Geschwindigkeit ankommt, aber hier ging es um eine Tastenabfrage, wo es 0 auf Geschwindigkeit ankommt, weil der Prozessor eh nur darauf wartet, daß endlich mal ne Taste gedrückt wird, und da find ich sowas einfach unangemessen.


    Lasst doch einfach auch mal die Meinung anderer gelten, und hackt nicht ständig darauf rum, und damit bin ich hier raus...

  • Und natürlich handelt es sich hierbei um selbstmodifizierenden Code. Hier wird Code zur Laufzeit umgeändert. Da ist es egal, ob eine Routine sich selbst oder eine andere Routine ändert. Die CPU interessiert das auf dem hier nicht vorhandenden Abstraktionsgrad schon mal überhaupt nicht.

    Schon korrekt - auf Harvard-Architektur kunktioniert es nicht. Egal wer da modifiziert. Trotzdem macht es für mich noch einen Unterschied in Bezug auf den Programmierstil.

  • Aber würde sich das nicht auch durch einen Jump auf eine jeweilige konfigurierte Unterfunktion (und daher Adresse) realisieren lassen?

    So würde das ohnehin ablaufen müssen. Sechs Kopien der zwei originalen Hauptschleifen, mit ORA/INY, ORA/DEY, EOR/INY, EOR/DEY, AND/INY und AND/DEY. *In* den Schleifen will ich *keine* Tests drin haben, ob jetzt INY oder DEY bzw. ORA, EOR oder AND benutzt werden sollen - dann ist die Geschwindigkeit nämlich hinüber.


    Trotzdem macht es für mich noch einen Unterschied in Bezug auf den Programmierstil.

    Lach. Hast Du schonmal auf einer SPS programmiert? Da läßt Du von ganz allein die Finger weg von allzu "raffinierten" Sachen, wenn Du nicht willst, daß deine Maschine durch einen Programmierfehler zerstört wird.

  • Puh, es handelt sich hier im Assembly-Programmierung, wo einige Best Practices aus anderen Programmiersprachen deutlich laxer gehandhabt werden. Schließlich geht es meist darum, das letzte aus Speicher und Rechenzeit rauszuholen. Meine Programme strotzen vor (mehrfach für verschiedene Zwecke genutzten) globalen Variablen, üblen Stack-Manipulationen und Konstrukten wie dem guten alten $2c-Trick zum Überspringen eines Befehls. Man sollte alles tun was nötig ist, um ein Programm lesbar und wartbar zu halten, aber selbstmodifizierenden Code finde ich noch eines der kleinsten Übel in dem Zusammenhang, so es denn dokumentiert ist.

  • 64KB Mega Drive, 128KB SNES dazu jeweils noch etwas Video- und Soundram. Weiß ich schon immer solange es das I-Net gibt.
    Nur da läuft das Programm ab. Zwischendurch wird immer 'was vom Modul in dieses Ram nachgeladen u. (anschließend) ggf. entpackt.
    Bekommt man nur nicht immer mit (bei Another World auf dem lahmen SNES aber schon, ein gutes Beispiel), weil's relativ schnell geht. Nie sind die 512KB bis 4MB eines Moduls aufeinmal aktiv.

    Auch hier nochmal Einspruch, Euer Ehren.
    Das SNES kann an seinem Adressbus A bis zu 16 MB adressieren, weshalb diese Aussage einfach nur falsch ist. Ich bin kein SNES Programmierer, bin mir aber sehr sicher dass weite Teile des Codes nicht umkopiert werden sondern direkt aus dem ROM laufen - alles andere wäre Verschwendung von Ressourcen, und da tun sich Hersteller schwer mit.


    Beim Mega Drive sieht es ähnlich aus, die Kiste hatte zwar 64kB RAM, kann mit ihrem 68000er aber bis 16 MB adressieren. ROMgröße waren idR bis 32mbit, also 4 MB. Auch hier muss das Mega Drive also mitnichten umschalten um das ROM komplett sehen zu können und auch hier wäre ein umkopieren in den Speicher (meist) nicht wirklich sinnvoll.


    In der Regel dürfte der RAM in den Konsolen für Berechnungen und nicht-statische Tabellen genutzt werden, der Programmcode hingegen bleibt schön wo er hingehört (und auch problemfrei adressiert werden kann): im ROM.

  • Meine Programme strotzen vor globalen Variablen, üblen Stack-Manipulationen und Konstrukten wie dem gute alten $2c-Trick zum Überspringen eines Befehls.

    Den hat auch Commodore gerne benutzt (z.B. in der 4040 Floppy). Aber die haben auch um jedes Byte gekämpft. Vom Floppy DOS2 gab es ein Update, wo mit solchen Tricks ein paar Bytes freigemacht wurden, um irgendwelche Bugs zu beheben.


    Aber ohne Not? Warum?



    Lach. Hast Du schonmal auf einer SPS programmiert? Da läßt Du von ganz allein die Finger weg von allzu "raffinierten" Sachen, wenn Du nicht willst, daß deine Maschine durch einen Programmierfehler zerstört wird.

    Ja, habe ich. Und auch Embedded-Mikrocontroller-Steuerungen, wo reale Hardware dranhing (also Dinge, die schnell mal kaputt gingen oder sich zumindest notabschalteten)