Viele Sprünge usw - wie handhabt ihr das?

  • kbr schrieb:

    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.

    CommieSurfer schrieb:

    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.
    Yes, I'm the guy responsible for the ACME cross assembler
  • 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.
    ' war Anfang 2000 schonmal unter dem Kennnamen 'c64fan' hier unterwegs.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von CommieSurfer ()

  • 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?
  • ZeHa schrieb:

    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.
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von detlef ()

  • 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.
    GREETINGS PROFESSOR FALKEN
    A STRANGE GAME.
    THE ONLY WINNING MOVE IS NOT TO PLAY.
    HOW ABOUT A NICE GAME OF CHESS?
  • tulan schrieb:

    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:

    Quellcode

    1. [...]
    2. .Line_10
    3. LDA (scrn),Y
    4. ORA #$80
    5. STA (scrn),Y
    6. DEX
    7. BEQ Line_09
    8. LDA accu
    9. SBC dy
    10. BCS Line_11
    11. ADC dx
    12. INY
    13. .Line_11
    14. STA accu
    15. [...]
    Alles anzeigen
    ... 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...)
  • tulan schrieb:

    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

    Mike schrieb:

    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.
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von detlef ()

  • detlef, in meinem vorigen Post siehst Du, daß man auch noch was anderes austauschen kann als nur Adreßoperanden.

    Das soll nicht bedeuten, daß ich jetzt selbstmodifizierenden Code für das Beste seit geschnitten Brot halte. Ich wende die Methode halt an, wenn's geht und Sinn macht.
  • detlef schrieb:

    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.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Mike ()

  • enthusi schrieb:

    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.
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20

  • @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.
  • enthusi schrieb:

    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...
    Wer die Ironie findet, darf sie behalten ^^
  • Mike schrieb:

    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.
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20

  • tulan schrieb:

    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.

    detlef schrieb:

    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.
    ────────────────────────────────────────────────────────────
    Time of Silence - Time of Silence 2 Development Blog
    ────────────────────────────────────────────────────────────

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Claus ()

  • CommieSurfer schrieb:

    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.
    GREETINGS PROFESSOR FALKEN
    A STRANGE GAME.
    THE ONLY WINNING MOVE IS NOT TO PLAY.
    HOW ABOUT A NICE GAME OF CHESS?
  • Claus schrieb:

    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?


    Mike schrieb:

    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)
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20