Pixel über Array statt Poke setzen

Es gibt 10 Antworten in diesem Thema, welches 2.758 mal aufgerufen wurde. Der letzte Beitrag (3. Oktober 2015 um 11:07) ist von JeeK.

  • Mir hat's beim Anblick des Listings im Thread Bitte melde dich an, um diesen Link zu sehen. in den Fingern gejuckt, insbesondere bei dem Trick mit DIMF%(8000). Ich hatte mich schon lange gefragt, ob über das Array auch Grafik erzeugt werden kann (Antwort ja) - und ob es performanter ist (im konkreten Fall ... eher nein).

    Warnhinweis am Anfang:
    Das ist eine reine Machbarkeitsstudie. Der Ansatz ist robust wie ein Kartenhaus - eine unbedachte Änderung im Code und die Grafikerzeugung macht Murks. Nett, es mal gesehen bzw. gemacht zu haben. Ich hab viel über Speicherverwaltung im C64 gelernt.

    Vorüberlegungen:
    Ausgangspunkt bei meiner Überlegung war das Integer-Array F%. Integer ist beim C64 ein 16Bit-Zweierkomplement, erstes Bit ist das Vorzeichenbit (Wert -32768), der Rest ganz normal (16384, 8192,...,1). Abgelegt wird der Integerwert wie folgt: zuerst das höherwertige Byte, dann das niederwertige. Da im Array alle Werte ohne Lücken aufeinanderfolgen kann über den Index indirekt auf den darunterliegenden Speicher zugegriffen werden.

    Zuerst heißt's herausfinden, wo genau das Array im Speicher liegt. Und da gibt's den großen Pferdefuß: die Zuordnung ist nicht fest. Sobald eine neue Variable eingeführt wird, verschiebt sich das Array - samt Inhalt (und aus Grafik wird Murks). Ergo: zuerst alle Variablen definieren und erst danach herausfinden, wo das Array liegt. Am besten das Array als letztes anlegen - so gibt es kein zeitraubendes Verschieben durch neue Variablen und die Adresse kann über die Endeadresse des Arrayspeichers (in $31,$32 bzw. 49,50) ermittelt werden und damit der Startbereich des Grafikspeichers in F%. Es macht das Leben einfacher, wenn man in dem Zuge gleich dafür sorgt, dass die Arrayelemente auf einer geraden Adresse beginnen (falls das nicht der Fall ist - einfach eine Zahlenvariable einführen, der Speicher verschiebt sich dann um 7 Byte).

    Genug der Worte, hier der abgewandelte Code aus dem anderen Thread - er hat leider nicht gewonnen durch die Eingriffe. Ich hab' versucht, den Kern und die Bedeutung der Variablen beizubehalten.

    Die wichtigsten Zeilen:
    13 - Ggf. Verschiebung des Speichers durch Hinzufügen der Variable QQ
    14 - Berechnung, an welchem Index die Speicheradresse 8192 liegt. Danach wird auch keine Variable mehr hinzugefügt
    29 - Bildpunkt setzen

    Viel Spaß beim Ausprobieren und Experimentieren. Kleines Beschleunigungspotenzial sehe ich noch in Zeile 29 bei int(xp/z8)*z4, da könnte man ein Wertearray für xp verwenden.

    Ach ja, die Zeiten:
    Das ursprüngliche Listing hatte im Vice 53:17 Minuten gebraucht, diese Fassung hier 54:09.

    Viele Grüße
    Tale-X

  • Sehr schön :hatsoff:.

    Minimale Verbesserung: Die Subroutine in Zl. 29 lässt sich mit Shortcuts noch in Zl. 25 zwängen -> 8 Sek. :beer: .

    Finde es aber irgendwie recht wenig, dafür, dass die SR 7.856x angesprungen wird... :hae: ?

    Bitte melde dich an, um diesen Link zu sehen. Bitte melde dich an, um diesen Link zu sehen. Bitte melde dich an, um diesen Link zu sehen.

    Ex-TLI (The Level 99 Industries) & Ex-TNP (The New Patriots) & Ex-TEA (The East Agents) & ?

  • Sehr gute Idee, das mit dem Integer-Array! :thumbsup:

    Bisserl Herumfeilen geht vielleicht noch:
    Statt
    29 ad=li(yp)+int(xp/z8)*z4:f%(ad)=f%(ad)orbi%(xpandz7,ypandz1):return

    29 ad=li(yp)+(xpandm7)/z2:f%(ad)=f%(ad)orbi(xpandz7,ypandz1):return

    Wobei z1, aber auch z7 (geht gerade noch) viel früher angelegt werden sollten (unter den ersten 10 Variablen).
    bi() als Integer-Array bringt nichts, außer Platz, der aber bei 14 Elementen auch nicht das Kraut fett macht.
    Im Gegenteil, das Parsing von "%" und die Konvertierung Integer-Float kostet auch noch Zeit (1,5 %).
    In Summe bewegt sich die Ersparnis (nicht Gesamtlaufzeit, nur bezogen auf diese Zeile) dann bei knapp 3,5 %.

    Wie schon vorher angemerkt bringt das Wegoptimieren des GOSUB-RETURN freilich noch viel mehr. ;)

  • Die Idee beschäftigte mich schon vor Urzeiten, als das erste Mal das Thema Löschen mit Array aufkam. Außerdem hatte sich bei mir irgendwie festgesetzt, dass die Implementierung des Poke-Befehls ineffizient sein soll (hab' ich aber nie überprüft). Von daher musste ich das einfach mal ausprobieren...

    Die Modifikationen bringen etwas, allerdings auch beim Ausgangsprogramm - das leider so schon nen kleinen Tick schneller ist.

    Was mich fuchst - eigentlich ist das Array *deutlich* schneller:


    Hmmm...

    Wissen ist das einzige Gut, das sich beim Teilen vermehrt. Also seid vorsichtig damit!

  • Zitat von Tale-X

    Außerdem hatte sich bei mir irgendwie festgesetzt, dass die Implementierung des Poke-Befehls ineffizient sein soll (hab' ich aber nie überprüft).


    Nun ja, das ist eigentlich nicht verwunderlich: POKE muß nun mal einen numerischen Ausdruck auswerten, der dann im Bereich 0..65535 liegen muß, auf ein Komma testen, und dann einen zweiten numerischen Ausdruck auswerten, der dann auf den Bereich 0..255 überprüft wird. Und das alles bloß, um anschließend ein einziges popliges Byte irgendwo im Speicher zu ändern.

    Früher hat man in diesem Zusammenhang gern von 'Mächtigkeit' der jeweiligen Befehle gesprochen. Soll heißen, wie viel an nachvollziehbarer Aktion geschieht, wenn der Befehl verwendet wird. So gesehen, dürfte POKE tatsächlich einer der am wenigsten mächtigen Befehle sein. Umgekehrt ist z.B. in einer Grafik-Erweiterung der Linienzieh-Befehl extrem mächtig.

    Anderes Beispiel: wenn man ein aus mehreren Zeichen im Textbildschirm bestehendes Objekt mit POKE bewegen soll, wird das schnell recht langsam. Setzt man an Stelle dessen einmal den PRINT-Befehl ein, kann man da durchaus eine (positive!) Überraschung erleben.

    Auf die Weise hab' ich beispielsweise mal die Bergkette in einem Port von Moon Patrol auf dem VC-20 bewegt. :)

    ...

    Deine zwei Beispiele oben sind übrigens nicht gut vergleichbar. Das Auswerten der 255 ist gegenüber der -1 echt "teuer". Setz' mal in beiden Fällen den Dezimalpunkt für 0 ein und vergleich dann nochmal: ich komm dann auf 243 und 366 (oder jeweils auch mehr, je nachdem wie lange man auf der RETURN-Taste hängenbleibt, übrigens... 8\|) und da im zweiten Fall auch doppelt soviele Befehle und Ausdrücke ausgewertet werden müssen, steht POKE auf einmal gar nicht mehr so schlecht da.

  • Die Idee beschäftigte mich schon vor Urzeiten, als das erste Mal das Thema Löschen mit Array aufkam. Außerdem hatte sich bei mir irgendwie festgesetzt, dass die Implementierung des Poke-Befehls ineffizient sein soll (hab' ich aber nie überprüft). Von daher musste ich das einfach mal ausprobieren...

    Die Modifikationen bringen etwas, allerdings auch beim Ausgangsprogramm - das leider so schon nen kleinen Tick schneller ist.

    Was mich fuchst - eigentlich ist das Array *deutlich* schneller:
    [..]
    Hmmm...


    Der Vergleich ist nicht ganz fair. POKE ist so primitiv, sowas kann man gar nicht ineffizient im Interpreter machen. Das Problem ist die 2x die Ausdruckauswertung für die Parameter. Im Speziellen das "255". Das Parsing baut einen Float-Wert zusammen und für jede weitere Ziffer (bezogen auf eine einstellige Zahl) fällt eine Float-Multiplikation mit 10 an. Das kostet. Und natürlich hat ja die POKE-Variante ja den doppelten Schleifen-Overhead.

    Wenn der POKE-Wert (aber auch bei der Array-Variante) in einer Variable ist, ist eine Art Waffengleichheit gegeben: in beiden Fällen muss 2* in der Variablen-Tabelle gesucht werden (Index, Wert). Bei der Arrayvariante dann aber auch noch in der separaten Array-Tabelle und das Feldelement muss berechnet werden (sind immerhin auch in 16-Bit-Arithmetik durchgeführte Multiplikationen, zumindest eine, glaub ich).

    Im obigen Beispiel braucht die Array-Variante 50 % von der POKE-Variante.
    Wenn man das mit Variablen macht, denn reduziert sich der Vorsprung nur noch auf 70 % Reduktion. D.h. POKE ist an sich schneller, d.h. der Array-Zugriff ist an sich aufwändiger (Feldelement berechnen, mehr BASIC-Text zu parsen).

  • Stimmt, ich unterschätze das Parsen des Interpreters, Compiler versauen einen in der Beziehung :whistling:

    Hab' mal beides auf Variablen umgestellt - a%(i)=i% und pokea,i. Dann ist Array max. 25% schneller. Spannend ist der Unterschied bei den verschiedenen i% Werten.

    Bei i% scheint es umso länger zu daueren, je mehr bits gesetzt sind und umso höherwertiger sie sind. -1 dauert am längsten (klar alle Bits gesetzt). Bei den einzelnen Bits: 16384 am Kürzesten. 8192 dauert einen kleinen Hauch länger... usw usf. bis 1 (bei meinem Programm sind es jeweils 5 TI-Einheiten). Nur -32768 schlägt aus der Reihe, das braucht ungefähr solange wie 2048. Och nöööö... wenn ich einem INTEGER-Array eine Fließkommazahl zuweise ist er in der Regel schneller als bei einer INTEGER-Zahl. Das hat dann aber nichts mehr mit Interpreter vs. Compiler zu tun, sondern dass Basic V2 schräg ist.

    Aber der Vorteil schrumpft deutlich und wenn die Auslese und Oder-Verknüpfungen bei Ganzzahlen ähnlich schleichen wie der zum setzen wundert mich gar nix mehr.

    Wissen ist das einzige Gut, das sich beim Teilen vermehrt. Also seid vorsichtig damit!

  • Würde der Parser evtl noch schneller arbeiten, wenn vorher mit DEFINT die Vars deklariert werden? Fällt damit das ständige Parsen des "%"-Zeichens weg?

    E D I T
    Der C64 mit Basic V2 hat gar kein DEF (TYPE) (RANGE), oder?

  • ZeroZero: richtig, das BASIC V2 auf dem C64 hat keine DEF TYPE Anweisung.

    Tale-X: sobald Integervariablen beteiligt sind, ist immer auch eine Int <-> Float Wandlung beteiligt. In Verbindung mit den BOOLEschen Operatoren AND, OR und NOT wird man aber gleich doppelt gestraft: während die normale Ausdrucksauswertung in Float arbeitet, arbeiten die BOOLEschen Operatoren tatsächlich intern mit Int - sie können aber nicht wissen, daß (bzw. ob) die Ausdrücke (links und) rechts von ihnen "reines" Int sind und werten sie darum zunächst als Float aus!

    Das heißt z.B. bei 'R% = S% AND T%' werden S% und T% zunächst nach Float und direkt wieder nach Int gewandelt, das Ergebnis von Int nach Float und bei der Zuweisung wieder nach Int. :drunk:

    Aus diesem Grund hab' ich das BI() Feld auch als Float belassen.

    Integer-Variablen machen wirklich nur bei großen Feldern zur Platzersparnis Sinn.

  • Ich schätze, der Unterschied wäre nur minimal. Der Haken ist, dass wohl alles über Fließkommazahl geht. Bei a%=b% wird zuerst Integer in Fließkomma und dann Fließkomma in Integer umgewandelt.

    Und so ist auch mein Testergebnis bei 1000 Zuweisungen: a=b ist schneller als a%=b das schneller als a%=b% ist. Die Zeiten (nach ti): 140 zu 160 zu 180, also nicht die Welt

    Wenn's um den letzten Prozentpunkt Performance geht - nicht auf Integer setzen...

    Wissen ist das einzige Gut, das sich beim Teilen vermehrt. Also seid vorsichtig damit!

  • Stimmt, ich unterschätze das Parsen des Interpreters, Compiler versauen einen in der Beziehung :whistling:

    Hab' mal beides auf Variablen umgestellt - a%(i)=i% und pokea,i. Dann ist Array max. 25% schneller. Spannend ist der Unterschied bei den verschiedenen i% Werten.

    Bei i% scheint es umso länger zu daueren, je mehr bits gesetzt sind und umso höherwertiger sie sind. -1 dauert am längsten (klar alle Bits gesetzt). Bei den einzelnen Bits: 16384 am Kürzesten. 8192 dauert einen kleinen Hauch länger... usw usf. bis 1 (bei meinem Programm sind es jeweils 5 TI-Einheiten). Nur -32768 schlägt aus der Reihe, das braucht ungefähr solange wie 2048. Och nöööö... wenn ich einem INTEGER-Array eine Fließkommazahl zuweise ist er in der Regel schneller als bei einer INTEGER-Zahl. Das hat dann aber nichts mehr mit Interpreter vs. Compiler zu tun, sondern dass Basic V2 schräg ist.

    Aber der Vorteil schrumpft deutlich und wenn die Auslese und Oder-Verknüpfungen bei Ganzzahlen ähnlich schleichen wie der zum setzen wundert mich gar nix mehr.


    Das Integer immer mit Floats gerechnet werden hat ja Mike schon ausführlich erklärt. Vielleicht ergänzend zu oben: Die Variation in der Zeit bei unterschiedlichen Werten kommt wohl von der Konvertierung Integer-Float. Eigentlich eine relativ einfache Operation, aber da die Float im "normalisierten" Format sind, fallen dann bei der Mantissennormalisierung, d.h. die gesamte Mantisse (4 Bytes) muss iterativ mit Schiebeoperationen so oft verschoben werden, bis das höchstwertige Bit quasi ganz "links" ist. D.h. z.B. -1 (bei Float ist das Vorzeichen extra) ergäbe das dann 1, also %0000 0000 0000 0001 und dieses eine Bit muss dann ganz nach links wandern. Die Trivialoptimierung, Nullbytes der Mantisse (rechts davon) nicht mitzuverschieben, verringert den Aufwand nur marginal. Das also bei Integer zu Float. Ähnliches Spielchen dann in die andere Richtung. ;)
    Das ist aber keine Besonderheit von BASIC V2, das wäre bei allen Interpretern so, die die Zahlen so darstellen, wenn man mal davon absieht, dass V2 hier generell nur mit Float rechnet (das war aber wirklich dem Platzmangel im ROM geschuldet - beim BASIC 3.5 oder 7.0 hat man sich dann auch nicht mehr getraut Bewährtes gravierend umzuschreiben und wohl eher auf neue Kommandos Wert gelegt).