Hallo Besucher, der Thread wurde 10k mal aufgerufen und enthält 92 Antworten

letzter Beitrag von WebFritzi am

Bytes aus Dateien schneller auslesen

  • Dabei müsste ich wissen, wo der Stub aufhört.

    Am Ende des BASIC-Programms stehen drei Nullen: eine für das Zeilenende der letzten Zeile, zwei weitere für den 'leeren' Link-Pointer.


    Ab $0810 kannst Du also jetzt deine Daten zwischen Stub und Nutzlast ablegen. :)


    ...


    Bei sowas gibt's übrigens den Trick, den Speicher mit einem Monitor vorher mit einem bekannten nicht-0-Wert zu füllen (also, nach einem NEW von $0803 bis $9FFF). Ich nehm meistens $2A dafür. Dann erkennt man recht gut, was noch zum BASIC-Programm gehört.


    Im Anhang ist bei Interesse die Diskette.

    Ich hab's mir mal angeschaut. Nice!


    Bei dem Konstrukt in den Zeilen 220 und 280 mußt Du etwas aufpassen. Am Ende des Tages errechnet das Programm einen Wert für TN, der von TI nie erreicht werden kann, und dann hängt das Programm. Das geht besser so:

    Code
    1. 220 TN=TI
    2. 230 GETA$
    3. [...]
    4. 280 IFABS(TI-TN)<SPTHEN230

    Das GOTO ist hier überflüssig und kann weggelassen werden.

  • So, Mike. Es hat alles ganz toll geklappt. Rund die Hälfte meiner Daten (mitsamt der Sprites) konnte ich dank deinem Tipp unterhalb des BASIC-Starts (jetzt: $1000) positionieren. Geiler Trick! :thumbsup: Danke auch für die anderen Tipps aus Beitrag #41. Habe sie eingearbeitet.


    Es ist leider nur die Hälfte der Daten, weil die andere zum Teil aus ML-Prozeduren besteht, in denen explizit auf gewisse Speicheradressen zugegriffen wird, und ich echt zu faul bin, die alle nochmal durchzugehen und anzupassen.


    Ebenfalls fülle ich einen zweidimensionalen BASIC-Array mit Daten. Diese hätte ich natürlich auch in den Speicher vorm BASIC schreiben können, aber dann müsste ich die alle einPEEKen, was sicherlich langsamer ist als das Lesen aus der Datei, oder?


    Wenn ich nun noch


    1. die Geschwindigkeit des Auslesens verbessern könnte (siehe Threadtitel) und

    2. die Größe der Datei verringern


    könnte, wäre das toll. Hast du (oder irgendjemand anderes) dahingehend Tipps für mich?

  • Wenn du die binären Data-Pokes auch binär abspeicherst, solltest du eigentlich schon ein gute Kompression haben.

    Mit ROM-RAM ist allerdings eine Kompression überflüssig, da du den gesamten Basic-Programmspeicher nutzen kannst und die Grafikangelegenheiten in den oberen 16 kB des C64, also außerhalb des Programm-RAMS stattfinden. Wenn die Daten binär, z.B. im Anhang, vorliegen ist aber natürlich die Installation einfacher und schneller.


    Schönen Gruß.

  • Wenn du die binären Data-Pokes auch binär abspeicherst, solltest du eigentlich schon ein gute Kompression haben.

    Das ist es ja, tue ich nicht. Bisher werden alle Daten (Bytes) als String gespeichert. Ich denke, das macht alles langsam und groß. Ein Weg, die Daten echt binär abzuspeichern und sie dann - ohne Konvertierung - direkt als INTs zu laden, wäre daher schon mal gut.

  • WebFritzi:
    Du meinst vermutlich als Bytes. INTEGER Variablen haben 2 Byte, und BYTES 1 Byte.
    Für das Laden ist ROM-RAM natürlich ideal, da du die Datei direkt nach 49152 ins obere RAM, außerhalb des Basic-RAM´s laden kannst.
    Die Sprites sind dann auch gleich installiert und du mußt nur noch die Spritepointer setzen.
    Ansonsten hat Mikes Methode natürlich den gleichen Vorteil, daß die Installation entfällt und die Sprites nur noch durch Setzen der Pointer übernommen werden.
    Das ist technisch betrachtet auch schon optimal.
    ROM-RAM bietet natürlich die weitere Möglichkeit zusätzlilch eigene Zeichen zu installieren z.B. Deutsche Umlaute oder Sonstiges.


    Schönen Gruß.

  • BIF Ich verstehe ja, dass du dein ROM-RAM bewerben willst ;-). Ich habe mein Programm aber nun soweit fertig und suche einfach nur eine Möglichkeit, Bytes binär auf Diskette zu speichern und auch wieder als solche einzulesen. Das muss doch möglich sein. Wenn nicht unter BASIC, dann wenigstens mit ML.

    Eine weitere Möglichkeit, Daten im BASIC-Programm zu integrieren, ist PRINT. Ist sehr schnell, nicht ganz so sparsam wie Rohdaten, aber wesentlich sparsamer als DATA-Zeilen.

    will hat dafür ein kleines Programm gestrickt. https://csdb.dk/release/?id=177079

    Das heißt, ich könnte meine Daten in Strings verwandeln, die ich dann in den Speicher PRINTe? Hört sich sehr interessant an. Nur leider verstehe ich das Program von Will nicht. Was tut es?

  • Also binäres Speichern auf Diskette.

    z.B.

    10 :open1,8,1,"name":cmd1:fori=atoe:printchr$(peek(i));:next:print#1,;:close1
    oder
    10 :open8,1,1"name":cmd1:fori=atoe:reada:printchr$(a);:next:geta$:close1

    Also wenn du die Daten binär in eine Datei schreiben willst, dann mußt du sie als Zeichen abspeichern.
    Und beim Lesen der Datei die Daten zeichenweise mit get oder get# einlesen.


    Schönen Gruß.

  • BIF Das hört sich doch schon mal interessant an. Vielen Dank. Wofür ist das GETA$ in der zweiten Variante? Und wie lese ich die Daten dann ein? Etwa so:

    Code
    1. 10 OPEN1,8,1,"NAME":FORI=1TO100:GET#1,A:POKEL(I),A:NEXT:CLOSE1

    Und wie verwende ich GET ohne #? Das liest ja aus dem Tastaturpuffer. Muss ich dann also irgendwie die geladenen Zeichen in den Tastaturpuffer umlenken?

  • Code
    1. 10 OPEN1,8,1,"NAME":FORI=1TO100:GET#1,A:POKEL(I),A:NEXT:CLOSE1

    Das ist natürlich Quatsch. Wenn, dann so:

    Code
    1. 5 Z$=CHR$(0)
    2. 10 OPEN1,8,1,"NAME":FORI=1TO100:GET#1,A$:POKEL(I),ASC(A$+Z$):NEXT:CLOSE1

    (siehe auch Beitrag #19 von JeeK).


    Die SEQ-Datei ist nun von 12 Blocks auf 3 Blocks geschrumpft. Allerdings macht die ASC()-Konvertierung das Ganze wiederum langsamer. Die Ladezeit mit Strings ist um ca. eine Sekunde schneller. Schade.

  • Wenn Du die Datei binär als PRG speicherst, kannst Du sie mit Hilfe der LOAD-Routine im Betriebssystem (ab $f49e) direkt laden, ohne dass die BASIC-Zeiger durcheinanderkommen.


    Hier der Code dazu:

    Code
    1. 9 F$="SMON $C000 ":LF=8
    2. 10 FORI=1TOLEN(F$):POKE678+I,ASC(MID$(F$,I,1)):NEXTI
    3. 11 POKE183,LEN(F$):POKE184,1:POKE85,1:POKE186,LF:POKE187,167:POKE188,2
    4. 12 POKE780,0:POKE781,0:POKE782,192:SYS62622

    Der Dateiname muss nach F$, die Nummer des Laufwerks nach LF.


    Zur Erklärung: Zeile 10 speichert den Dateinamen ab 679 - da ist etwas Platz. In Zeile 11 kommt nach 183 die Länge des Dateinames, nach 187/188 die Adresse (also 679). 184 ist die Dateinummer, 185 die Sekudäradresse (hier 1, damit ab beliebige Adresse geladen wird) und 186 die Gerätenummer. In Zeile 12 sagt 780, das LOAD (und nicht VERIFY) ausgeführt wird; nach 781 und 782 kommt die Zieladresse (im Beispiel 49152).


    Ich hab's mit dem SMON probiert - hat geklappt.


    Wenn man beliebige Daten laden müssen, werden die ersten beiden Byte der Datei - die normalerweise die Startadresse enthalten - ignoriert. Das muss beim Speichern natürlich berücksichtigt werden.

  • RAM-Bereich speichern:

    Code
    1. SYS57812(N$),<device>:POKE193,<start_lo>:POKE194,<start_hi>
    2. POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496

    <start> ist inklusiv, <end> ist exklusiv, N$ enthält den Dateinamen. Das hier sollte man i.A. immer zum Speichern von RAM-Bereichen hernehmen. Für Floppy-Betrieb macht es keinen Unterschied zur folgenden Methode, aber nur die gerade genannte Methode erlaubt es bei Tape-Betrieb, die Datei an eine andere Adresse zu laden.



    RAM-Bereich speichern (und Ladeadresse bei Tape erzwingen):

    Code
    1. SYS57812(N$),<device>,1:POKE193,<start_lo>:POKE194,<start_hi>
    2. POKE780,193:POKE781,<end_lo>:POKE782,<end_hi>:SYS65496

    Bei Disk ist das Ergebnis das gleiche wie zuvor. Für Tape erzwingt diese Methode aber das Laden des Speicherblocks an die ursprüngliche Adresse, egal welche Sekundäradresse man angibt!



    RAM-Bereich laden ('relatives' oder 'relozierendes' Laden):

    Code
    1. SYS57812(N$),<device>:POKE780,0:POKE781,<start_lo>:POKE782,<start_hi>:SYS65493

    ... oder SYS57812(N$),<device>,0 - lädt die Datei an Adresse <start>. Wenn bei Tape beim Speichern ",1" als Sekundäradresse angegeben wurde, gilt allerdings immer die Angabe auf Band!



    RAM-Bereich laden ('absolutes' Laden):

    Code
    1. SYS57812(N$),<device>,1:POKE780,0:SYS65493

    Lädt die Datei dahin, von wo sie abgespeichert wurde.


    Bei beiden genannten LOAD-Varianten wird das BASIC-Programm hinter SYS65493 normal fortgesetzt, es erfolgt kein ungewollter Neustart des Programms.


    ...


    Zur Erklärung:


    SYS57812 ruft die Routine im BASIC-Interpreter auf, die Dateinamen und Geräteadresse parst. Das 'fehlende' Komma zwischen SYS und Dateiname ist schon so richtig; die Klammern stehen da, damit die Variable dennoch nicht an der SYS-Adresse 'klebt' ... (also, aus ästhetischen Gründen ^^) und es keinen ?SYNTAX ERROR gibt, falls E...$ als Variable für den Dateinamen verwendet wird (Pop-Quiz: warum kommt dann ein Fehler?)


    SYS65493 und SYS65496 rufen KERNAL LOAD und KERNAL SAVE sauber über die Sprungtabelle am Ende des KERNALs auf. Ein vorhandener Schnell-Lader freut sich. :)


    Das dämliche Konstrukt mit IFA=0THENA=1:LOAD"blubb",8,1 bitte nicht mehr verwenden. Danke.


    Beim Speichern auf Disk vorher mit OPEN15,<device>,15,"S0:name":CLOSE15 eine alte Datei gleichen Namens löschen und bitte nicht Save-mit-Replace verwenden. Der User wird's danken.


    Mit DN=PEEK(186) bekommt man die Geräteadresse heraus, die zuletzt verwendet wurde. PEEK(186) nicht direkt im SYS57812 Aufruf verwenden, da SYS57812 die Adresse 186 auf 1 setzt (Default Tape), bevor das Parsing zu dem PEEK(186) kommt!



    Quelle: Denial - ROM calls and other tricks, für den VC-20.

  • Eine andere Möglichkeit zum Nachladen ist die Verwendung eines Zählers. Man kann damit auch mitten im Programm noch was nachladen, indem man vor der Lade-Anweisung den Zähler bzw. den Sprungindex setzt und im ONGOTO-Befehl in der ersten Zeile an der richtigen Stelle die Zeilennummer notiert, bei der es nach dem Laden weitergehen soll.


    Beispiel für mehrere Nachladungen:


    Das heißt, ich könnte meine Daten in Strings verwandeln, die ich dann in den Speicher PRINTe?

    Genau, wobei es da wieder zwei verschiedene Möglichkeiten gibt.

    1. Das direkte PRINTen dorthin, wo der Code hin soll (dazu muss der Bildschirmspeicher temporär umadressiert werden). Das ist das Thema, das will behandelte.

    2. Das PRINTen in den Billdschirmspeicher (dazu kann der Bildschirmspeicher temporär umadressiert werden) und anschließendes Verschieben per PEEK und POKE.


    Ein Beispiel mit drei Sprites, die per Methode 1 in den Speicher gelangen, hat will ja bereits auf der Diskette. Zum Vergleich hat er auch die langsamere DATA-Zeilen-Methode dazugelegt.


    Ein Beispiel für eine kleine BASIC-Erweiterung (Joystickabfrage, PRINT AT und GOTO X), die per Methode 2 in den Kassettenpuffer transferiert wird, habe ich in meinem Spiel "Konsumwahn":


    Als DATA-Zeilen hätte es zwei volle PUR80-Zeilen gebraucht.


    Das Hilfsprogramm von will erzeugt solch einen PRINT-String bzw. auch gleich die BASIC-Zeilen dafür. Die Daten entsprechen dann bei der Ausgabe dem Bildschirmcode. Da man aber invertierte Zeichen nicht direkt in einem String angeben kann, müssen diese mit einem zusätzlichen Steuercode zum Invertieren erzeugt werden; das betrifft die Codes oberhalb 127. Deswegen ist es als String etwas mehr an Daten als man dann im Speicher hat.

  • Eine andere Möglichkeit zum Nachladen ist die Verwendung eines Zählers. Man kann damit auch mitten im Programm noch was nachladen, [...]

    Nein, das geht eben nicht. Auch mit deiner ON .. GOTO Variante des kaputten "IFA=0THENA=1:LOAD"blubb",8,1" wird beim ungewollten Neustart des Programms der Stapel zurückgesetzt, alle aktiven FOR-Schleifen und GOSUBs sind damit 'für die Fisch'!



    Wenn man mit meiner Methode mehrere Dateien nachladen will, bitte:

    Code
    1. ...
    2. 10 N$="FILE 1":GOSUB xx
    3. 15 N$="FILE 2":GOSUB xx
    4. 20 N$="FILE 3":GOSUB xx
    5. ...
    6. xx SYS57812(N$),8,1:POKE780,0:SYS65493:RETURN

    ... oder etwas ausführlicher mit vorheriger Ermittlung der Geräteadresse.

  • Nein, das geht eben nicht. Auch mit deiner ON .. GOTO Variante des kaputten "IFA=0THENA=1:LOAD"blubb",8,1" wird beim ungewollten Neustart des Programms der Stapel zurückgesetzt, alle aktiven FOR-Schleifen und GOSUBs sind damit 'für die Fisch'!

    Und deswegen geht das nicht? Der Code ist aus einem uralten funktionierenden Programm von mir, ein Iso-3D-Editor. Es sollte klar sein, dass man das nicht aus einer Schleife oder Sub-Routine heraus aufruft - allein schon, weil man diese dann unordentlich verlässt. Das würde man dann schon merken, dass dies nicht geht. Es ist aber ansonsten eine bewährte Alternative, um vor allem am Anfang des Programms etwas nachzuladen (so wie bei diesem Tetris-Spiel erforderlich), zumal es auf reinem BASIC basiert und nicht auf POKEs und SYSs. Wurde schon in vielen Programmen gesehen und auch in der 64'er behandelt.


    Mir ist es egal, was WebFritzi letztendlich verwendet, aber alle gängigen oder möglichen Methoden sollten hier vorgestellt werden dürfen, denke ich. Man kann ja die Vor- und Nachteile auch aufzählen. Die Entscheidung liegt letztendlich bei ihm.

  • atomcode: Eine derart ausführliche Ansage verdient eine ebenso ausführliche Entgegnung:

    Und deswegen geht das nicht? Der Code ist aus einem uralten funktionierenden Programm von mir, ein Iso-3D-Editor.

    Meine Code-Zeilen oben habe ich schon öfter verwendet, als ich noch irgendwie abzählen könnte.


    Es sollte klar sein, dass man das nicht aus einer Schleife oder Sub-Routine heraus aufruft - allein schon, weil man diese dann unordentlich verlässt. Das würde man dann schon merken, dass dies nicht geht.

    Es sollte nicht nur 'klar' sein, es geht überhaupt nicht, aus dem von mir genannten Grund (Stapel wird zurückgesetzt). Damit geht z.B. der Unterprogrammaufruf, den ich in Beitrag #56 verwende, einfach mal überhaupt nicht.


    Es ist aber ansonsten eine bewährte Alternative, ...

    Es ist technisch gesehen eine absolut miserable Methode. Die Notwendigkeit, den Seiteneffekt des ungewollten Programm-Neustarts mit irgendwelchen Programm-Konstrukten in den Griff bekommen zu müssen, macht sie nicht besser.


    ... um vor allem am Anfang des Programms etwas nachzuladen ...

    Für was anderes ist sie auch nicht zu gebrauchen.


    (so wie bei diesem Tetris-Spiel erforderlich),

    WebFritzi könnte hier alles in einen One-Filer packen (eigentlich haben wir hier ein XY-Problem). Das aber nur so nebenbei.


    zumal es auf reinem BASIC basiert und nicht auf POKEs und SYSs.

    Der Rest des Programms kommt auch ohne POKE und SYS aus? Echt?


    Wurde schon in vielen Programmen gesehen und auch in der 64'er behandelt.

    Das macht die IF ... LOAD Methode trotzdem nicht besser.


    Mir ist es egal, was WebFritzi letztendlich verwendet, aber alle gängigen oder möglichen Methoden sollten hier vorgestellt werden dürfen, denke ich. Man kann ja die Vor- und Nachteile auch aufzählen. Die Entscheidung liegt letztendlich bei ihm.

    Es heißt ja immer, das Bessere ist der Feind des Guten. Nur - die IF ... LOAD Methode ist noch nicht mal das: gut.

  • Forum klemmt bei mir, und Quotes gehen auch nicht.


    >"Und deswegen geht das nicht? Der Code ist aus einem uralten funktionierenden Programm von mir, ein Iso-3D-Editor."

    >"Meine Code-Zeilen oben habe ich schon öfter verwendet, als ich noch irgendwie abzählen könnte."


    Die Betonung lag auf "funktionierenden". Das war ja nicht zufällig fett geschrieben. Weil du sagtest, es ginge nicht.

    Ich hab aber wiederum nicht behauptet, dass deine Methode nicht ginge, weswegen ich deren Verteidigung deinerseits nicht nachvollziehen kann.


    >"Es ist technisch gesehen eine absolut miserable Methode."

    Aber es ist eine weitere Methode. Warum gibt Stiftung Warentest eigentlich nicht nur das beste Produkt in der Tabelle an, sondern auch die schlechten, völlig unverständlich und unnötig. :rolleyes:


    >"Der Rest des Programms kommt auch ohne POKE und SYS aus? Echt?"

    Nein, aber um diese geht es hier gar nicht. Oder hab ich jemals behauptet, dass ich was gegen POKEs und SYSs hätte? Es zählen eben nicht nur die eigenen Präferenzen, auch wenn sich das manche nicht vorstellen können. Wenn man Merkmale aufzählt, heißt das nicht automatisch, dass man diese präferiert. ;) Davon abgesehen, sind manche POKEs unerlässlich, oder wie setzt du etwa die Hintergrundfarbe ohne POKE? Es gibt aber eben Leute, die möglichst wenig davon drin haben wollen. Und dann ist das eben so. Ich muss nicht jeden bevormunden; ich nenne Möglichkeiten. So kann man aus der Stiftung-Warentest-Tabelle durchaus auch ein Produkt kaufen, das nur mit ausreichend abgeschnitten hat, wenn man es möchte.


    >"Das macht die IF ... LOAD Methode trotzdem nicht besser."

    Nein, aber zeigt, dass es eine bekannte Methode ist, die man allein schon der Vollständigkeit halber aufzählen kann. Ich sagte "Eine andere Möglichkeit ist ..", ohne jede Wertung oder Empfehlung. Will ja hier nichts verkaufen, und WebFritzi ist vermutlich erwachsen und kann sich nach Kenntnisnahme aller Möglichkeiten und deren Vor- und Nachteile selbst entscheiden.


    Wo bleibt der Retro-Gedanke? Was haben wir denn nur damals gemacht, als es noch kein Internet gab, man alle möglichen und unmöglichen Dinge in Büchern und Zeitschriften nachlesen konnte und sich dann GANZ ALLEIN für eine Methode entscheiden musste? :huh: Wenn einer nach Möglichkeiten fragt, heißt das nicht, dass er selbst gar nicht mehr denken will.

  • OK, Leute. Tausend Dank für eure Vorschläge und Erklärungen. Mir ist gestern noch eine andere Möglichkeit in den Sinn gekommen, die ich heute versuchen werde umzusetzen. Meine Daten sind zweigeteilt. Ein Teil besteht aus ML-Routinen, der andere aus Zahlen, die ich in einen Array S(I,J) einlese. Was ich nun machen werde, ist folgendes: ich habe ja schon BASIC auf $1000 angehoben. Ab $0A00 ist noch ne Menge Platz. Ich werde einfach alles dahin speichern (ca. 700 Bytes). Die ML-Routinen verschiebe ich dann einfach mit der entsprechenden KERNAL-Methode an ihren Platz und die Daten für den Array lese ich einfach mit PEEK ein. Das ist schneller als jedes Laden von Diskette, welches ich mir dann komplett ersparen kann.