Hallo Besucher, der Thread wurde 5,1k mal aufgerufen und enthält 94 Antworten

letzter Beitrag von Zirias/Excess am

Map effizient auf Screen abbilden

  • Der C64DTV unterstützt auch die illegalen Opcodes des 6510.

    Gerade nochmal nachgelesen, die kamen bei DER Maschine wohl erst in Version 2: https://www.c64-wiki.com/wiki/C64DTV


    Edit: Ich meine ja nur, wenn man sie nicht WIRKLICH dringend braucht würde ich persönlich "illegal opcodes" lieber weglassen. Auf irgendwelchen Exoten (Emulatoren) gibt's sonst halt vielleicht mal unnötigerweise Probleme....

  • Indirekt (ZP),Y braucht 6 Zyklen.

    Laut C64-Wiki nur bei Grenzüberschreitung. Sonst 5. Aber ja, ist einer mehr als bei absoluter Adressierung.

    Edit: Einen Sinn sehe ich in (ZP),Y eigentlich nur für Code, der im ROM läuft wodurch self-mod logischerweise ausscheidet.

    Oh, das heißt, du verwendest eigentlich nie indirekte Adressierung? (für (ZP,X) sehe ich kaum einen Nutzen)


    Zu selbst-modifizierendem Code: Ich habe hier schon viele Stimmen dagegen gehört - auch von alt eingesessenen Leuten. Es ist halt sehr fehleranfällig. War mir sonst auch immer zu heikel. Jetzt habe ich einen Grund dafür gelernt: Geschwindigkeit.

  • Als kleine Inspiration hier mal der Code, der in Stoneage64 den Map-Ausschnitt auf den Bildschirm bringt:

    Ah, cool. Sehr interessant, danke.


    Dieses Repeat - geht das auch in ACME-Syntax im C64Studio? Das kannte ich so noch nicht. Sehr praktisch! So sieht es doch sehr übersichtlich aus.

    Yo, gibt es. Bei ACME/C64Studio nennt sich das !for:


    Code
    1. !for row = 0 to 22
    2. ldy $f800+($28*row),x
    3. lda tilecolors,y
    4. sta $fc00+($28*row),x
    5. !end
  • Wenn man eh schon in Assembler programmiert, finde ich es müßig, sich irgendwelchen philosophischen Einschränkungen zu unterwerfen. Man sollte nichts optimieren und dadurch den Code schlechter lesbar machen, wenn es nicht nötig ist. Fehlen einem aber die entscheidenden Zyklen, sollte man m.E. tun was nötig ist, was selbstmodifizierenden Code und illegale Opcodes einschließt.

  • Laut C64-Wiki nur bei Grenzüberschreitung. Sonst 5. Aber ja, ist einer mehr als bei absoluter Adressierung.

    Richtig, war in der Zeile verrutscht und hatte bei Store geschaut. Aber da braucht ja auch die ABS,X Version grundsätzlich 5 Cycles. Also ganz simple Formel, ZP-Pointer verwenden kostet pro Instruction ein Cycle mehr.


    In dem Zusammenhang kann man noch erwähnen, weil das ja auch halbwegs in den Thread passt: Am schnellsten geht's natürlich absolut, ohne Index. Heißt dann, ALLES unrollen, quasi nur noch LDA und STA, und nennt sich Speedcode. Das wird zumindest in Demos auch gemacht wo es anders nicht ginge. Schwierigkeiten sind natürlich, erstens mal wir der Code sehr groß und muss überhaupt noch ins RAM passen, zweitens hat man ein Problem wenn man eben DOCH von Laufzeitvariablen abhängt, was ja häufig der Fall ist. Die Lösung ist dann, dass der Speedcode erst zur Laufzeit generiert wird. Codegenerierung ist dann "next level" nach Selbstmodifikation :-D Unnötig zu erwähnen, dass man SO etwas wirklich nur DANN macht, wenn man gar keine andere Option hat, die flott genug wäre.


    Oh, das heißt, du verwendest eigentlich nie indirekte Adressierung? (für (ZP,X) sehe ich kaum einen Nutzen)

    Ich verwende sie selten. Die "Indirektion" durch Selbstmodifikation ist fast immer ebensogut lesbar, wenn man es vernünftig hinschreibt. Ab und zu verwende ich sie schon, wenn ein Pointer in der ZP eben wirklich komfortabler zu nutzen ist und der Code nicht zeitkritisch ist. Könnte mir auch Spezialfälle vorstellen, die sich durch Selbstmodifikation gar nicht optimieren lassen, z.B. wenn man den Pointer selbst recht häufig ändern muss...


    Zu selbst-modifizierendem Code: Ich habe hier schon viele Stimmen dagegen gehört - auch von alt eingesessenen Leuten. Es ist halt sehr fehleranfällig.

    Das kann ich im Kontext C64/6502 nicht nachvollziehen, zumindest nicht, wenn man es vernünftig aufschreibt. Wenn ich sowohl dem ZP-Pointer als auch meinem Instruction-Argument fürs LDA/STA ein Label verpasse, wieso ist es dann mehr oder weniger fehleranfällig? :S


    Was anderes wäre es ja, wenn man moderne Systeme betrachtet. Ein möglicher Angriffsvektor für Exploits ist es, das Programm selbst zu modifizieren, eine übliche Gegenmaßnahme ist "W^X", d.h. eine Page wird immer nur entweder writable (W) oder executable (X) gemappt. In einer solchen Konfiguration würde ein selbstmodifizierndes Programm schlicht nicht laufen. Dazu kommt die Sinnlosigkeit heutzutage, eventuelle Vorteile durch Selfmod werden ins Gegenteil verkehrt, weil das natürlich Pipelines etc in der CPU invalidiert und der Programmcode neu aus dem RAM geladen werden muss. Solches "Zeug" gibt es beim 6502 nicht, alles kommt direkt zur Ausführung aus dem RAM :-D


    Man sollte nichts optimieren und dadurch den Code schlechter lesbar machen, wenn es nicht nötig ist.

    Grundsätzlich: Ja. Optimieren IMMER nur da, wo man auch einen Engpass festgestellt hat.


    Beim Thema "Dinge auf den Bildschirm bringen" ist das aber eher die Regel als die Ausnahme :-D (Ich hab noch fast jede Bilschirmausgabe-Routine irgendwann mindestens ein mal umgeschrieben, damit sie schnell genug war...) Und die kleine Optimierung, statt Pointern in der ZP die Pointer direkt in den Programmcode zu schreiben (und dazu noch die Schleife über die Zeilen "unrollen", in die innere hinein), ist weit verbreitet, leicht zu verstehen und meiner Meinung nach auch in sehr lesbarer Form aufzuschreiben.

  • Noch ne Ergänzung, nicht direkt zum Thema, aber zum Kontext: Zu den praktisch schon "idiomatischen" Optimierungen auf dem C64 zählt ja auch diese Struktur für eine ISR:

    Code
    1. isr:       sta    rest_a+1
    2.            stx    rest_x+1
    3.            sty    rest_y+1
    4.            ;
    5.            ; actual IRQ handling here
    6.            ;
    7. rest_y:    ldy    #$ff
    8. rest_x:    ldx    #$ff
    9. rest_a:    lda    #$ff
    10.            rti

    Die Register derart per selfmod sichern spart schon einiges gegenüber der "traditionellen" Ablage auf dem Stack. Aufpassen muss man nur mit cli, falls die ISR reentrant sein muss geht es so natürlich nicht. Aber es ist wirklich common practice in C64 Code, und damit auch etwas, was ich von vorneherein "per default" immer so mache.

  • wenn es nicht auf Speicherplatz ankam, aber auf möglichst wenig Rasterzeilen hab ich damals tatsächlich auf

    LDA, STA, LDA, STA, LDA, STA.......

    ...RTS

    zurückgegriffen.

    Das tippen ist dann schon ne Arbeit für doofe 🤷‍♂️

  • Das tippen ist dann schon ne Arbeit für doofe 🤷‍♂️

    Ja, wenn man das von Hand macht .... :emojiSmiley-23:


    Eigentlich nimmt man da ja, wenn's statisch ist, eben solche !for oder .repeat Konstrukte seines Assemblers her :whistling: – und wenn's NICHT statisch ist muss man den Mist eh zur Laufzeit vorab generieren, aber DAS ist dann wirklich komplex/aufwändig....

  • Richtig, war in der Zeile verrutscht und hatte bei Store geschaut. Aber da braucht ja auch die ABS,X Version grundsätzlich 5 Cycles. Also ganz simple Formel, ZP-Pointer verwenden kostet pro Instruction ein Cycle mehr.

    Hups, der Fehler ist mir oben beim Zyklen zählen auch passiert: Da hier einmal gelesen und zweimal geschrieben wird, ist es ein Taktzyklus weniger, als ich geschrieben habe.

  • Yo, gibt es. Bei ACME/C64Studio nennt sich das !for:


    Code
    1. !for row = 0 to 22
    2. ldy $f800+($28*row),x
    3. lda tilecolors,y
    4. sta $fc00+($28*row),x
    5. !end

    Yo, geil. Danke!


    @Alle Vielen Dank, ich habe hier eine Menge gelernt.

  • WebFritzi wie gesagt, war mir klar, dass das irgendwie geht, ein Assembler der nicht mal simple Präprozessor-Schleifen bietet ist IMHO unbrauchbar. Leider gibt's für solche Dinge keinen einheitlichen Standard.


    Der Kickassembler geht besonders weit was solche "Präprozessor-Features" angeht, ist vermutlich deshalb in der Demo-Scene recht beliebt. Mein Fall ist er nicht, mir fehlen dafür andere Features ;-)

  • Was ist das?

    Naja, sowas wie eben !for oder .repeat. Ob der Assembler das nun wirklich in nem Präprozessor-Durchgang implementiert, kommt wohl drauf an. Naheliegend wäre es, erstmal sowas expanden, dann wirklich assemblieren.

    Also eine Art Hochsprachen Befehl im Assembler. Ist sowas nicht ineffizient?

  • Also eine Art Hochsprachen Befehl im Assembler. Ist sowas nicht ineffizient?

    Kommt drauf an in welcher Dimension :-D Diese Befehle (die einfach einen Code-Block zig mal wiederholen, angepasst mittels einer Laufvariablen) blähen natürlich den Code gewaltig auf, also in der Dimension RAM ist es "ineffizient". "Richtig" eingesetzt kommt dabei aber Code heraus, der schlicht schneller läuft. Ist übrigens ein altbekanntes Optimierungsproblem: Laufzeiteffizienz vs Speichereffizienz, pick one.


    Edit: Moderne Compiler gehen ganz ähnlich vor, ohne dass der Programmierer das je sieht: Schleifen werden automatisch "unrolled" WENN sich das aus Sicht des Optimierers "lohnt" (und technisch möglich ist). Oder wenn der Programmierer dem Compiler gesagt hat, dass er das möglichst immer tun soll (-funroll-loops für GCC oder clang).

  • Also eine Art Hochsprachen Befehl im Assembler. Ist sowas nicht ineffizient?

    Genauso wie der einfache Ersatz von Symbolen durch (numerische) Werte, den ein Assembler zur Assemblierzeit ausführt, sind diese Präprozessorbefehle (.FOR, .REPEAT, .IF) nur zur Assemblier- und nicht zur Laufzeit von Belang. Insbesondere generieren sie selbst keinen Code, sie steuern lediglich den Ablauf der Assemblierung!


    Im konkreten Fall wird ein ausgesuchtes Stück Quelltext mehrfach oder wahlweise assembliert, ggfs. mit angepaßten Operanden. Auch da gilt, daß sämtliche kompliziertere Berechnungen, die im Quellcode eine Adresse erzeugen, *nicht* anschließend so auch im Maschinencode ausgeführt werden. Im Maschinencode steht nur die vom Assembler bereits ausgerechnete (Basis-)Adresse.