Hallo Besucher, der Thread wurde 44k mal aufgerufen und enthält 337 Antworten

letzter Beitrag von Boulderdash64 am

Optimierungsgrad KERNAL /BASIC - fiktiver Talk

  • Ich sehe noch Optimierungspotential bei den vielen Stellen mit String-Ausgaben im Kernal. Beispielsweise hier:



    Würde ich eher so schreiben, wie eine for-loop in C:


    Code
    1. .,F5C1 A0 00 LDY #$00 Zähler setzen
    2. .,F5C3 C4 B7 CPY $B7 mit Länge des Filenamens vergleichen
    3. .,F5C5 F0 XX BEQ $F5CF
    4. .,F5C7 B1 BB LDA ($BB),Y Filenamen holen
    5. .,F5C9 20 D2 FF JSR $FFD2 und ausgeben
    6. .,F5CC C8 INY Zähler erhöhen
    7. .,F5CD D0 XX BNE $F5C3
    8. .,F5CF 60 RTS Rücksprung


    Evtl. kann man ja noch mehr sparen, man die ganzen Stringausgaben durch eine generische Ausgabe ersetzt,
    die nur die Adresse des Strings bekommt.

  • Meinst du so eine generische Stringausgabe wie im C116? Dort ist der Stringtext direkt nach dem JSR zu dieser Ausgaberoutine als DB (DataByte) eingebettet und nullterminiert.. und die besagte Routine korrigiert die Rücksprungadresse im Stack um die Länge dieses Strings...


    Das geht freilich nur mit situationsbezogen "festen" Strings und nicht, wenn in Abhängigkeit von einer Selekt-Variablen aus einer Liste gepflückt werden muss.


    Oder eine Übergabe des Stringstarts in den knappen Registern des 6502 mit allen länglichen Folgen (Register umständlich vorher auf Stack retten usw.)?


    Nötig ist meiner Ansicht nach eine Art 'Giganto-Tool' das folgendes leistet:
    - mit einer riesigen Datenbasis vergleichbar der F64-Wanderplatte und einem Skript, das sämtliche BASIC- und Maschinencode-Programme die je geschrieben wurden, ob gepackt oder nicht gepackt, parst, in eine compiler-ähnliche Baumstruktur überführt und sämtliche ROM-Aufrufe herausfiltert sowie nach Möglichkeit auch die gängigsten Tricks von Programmaufrufen durch verbogene Pointer usw. analysiert. Und im Idealfall nur wenige Spezialfälle (kompliziertere Selbsmodifikationen) für manuelle Nach-Analyse aussortiert.


    So dass man einen Überblick gewinnt, und nicht nur auf Vermutungen angewiesen ist.


    Hexworx: Du hast das Kopieren der VIC-Werte sogar noch um 1 Zyklus pro Durchlauf beschleunigt, weil STA $CFFF,X durch STA D000,X ersetzt wurde und die Schleife von BPL statt BNE abgeschlossen wird :)

  • Meinst du so eine generische Stringausgabe wie im C116? Dort ist der Stringtext direkt nach dem JSR zu dieser Ausgaberoutine als DB (DataByte) eingebettet und nullterminiert.. und die besagte Routine korrigiert die Rücksprungadresse im Stack um die Länge dieses Strings...

    Ja... vielelicht liesse sich auch eine Routine, die je nach Einsprung die Adresse aus den Bytes nach dem JSR oder z.B. vom Stack holt bauen.


    Dann wäre der Code-Reuse noch höher!

  • Und dann noch eine Unterscheidung ob mit Bit7=gesetzt terminiert wird (wie z.b. die Basic-Schlüsselwörter für LIST und Eingabeparser) oder mit Nullbyte, oder mit Längenbyte wie von dir oben skizziert.... oder sollte man vorher diese wilde Aufsplitterung in unterschiedliche Stringterminierungs-Arten erst beheben, bevor man eine Universal-Stringausgaberoutine plant?


    Kann man diese Routine auch für STR$ / VAL-Umwandlungen verwenden ..?

  • Wenn ich mich richtig erinnere, gibt es nirgends eine Schleife, die die Zeropage und den Bereich ab $200 mit solchen Tabellen initialisiert. Das muss doch wohl alles mit einzelnen Lda/Sta o.Ä. passieren?

    Der Flankengetriggerte Pin ist ein EINGANG. Kein Ausgang.
    Da kann und muss man nix "schreiben" um etwas auszulösen,
    da muss man nur sich zurücklehnen, auf-sich-zukommen-lassen-was-da-kommen-mag, warten und ggf. einen Interrupt bearbeiten.

    Mit Schreiben meinte ich "vorher aufs Band schreiben".

    Nachtrag: bei CA2 / CB2 des VIA könnte man sogar versuchen, die Polarität des Flankensensors bei jeder Flanke umzuschalten (togglen), vom abfragenden Interruptprogramm aus, dann könnte man im RAM-Speicher ein Flag mitführen, das per EOR#$01 o.ä. jederzeit dem Pegel des Portpins folgt! Das würde genau deinem Ziel entsprechen, jederzeit über den Pegel des Portpins bescheid zu wissen!

    DAS ist allerdings mal ein cooler Gedanke! Wenn man diesen IRQ-Eingang konfigurieren und ihm mitteilen kann, auf welchen Wechsel er reagieren soll, dann wäre das ne Lösung. Welcher Loader nutzt schondie IRQ-Funktion dieses Bits, wo eine Zählschleife, die das Bit testet, kürzer und genauer ist? Unter diesen Bedingungen macht ein Abfragen des Pegels wesentlich mehr Sinn und erlaubt schnellere Formate.

    Welche Adressen gäbe es denn überhaupt im BASIC bei $A000-$BFFF, wo jemand evtl. mal direkt reingesprungen sein könnte? Mir fällt da höchstens $BDCD ein (wird sonst wohl nur von der Einschaltmeldung [KERNAL $E43A] und bei LIST [BASIC $A6EA] genutzt). Aber sonst?

    Da muss ich mal in meinen alten Zettelsammlungen graben, auf denen ich mir nützliche Routinen gesammelt habe.

  • Zitat von hexworks

    Welche Adressen gäbe es denn überhaupt im BASIC bei $A000-$BFFF, wo jemand evtl. mal direkt reingesprungen sein könnte? Mir fällt da höchstens $BDCD ein (wird sonst wohl nur von der Einschaltmeldung [KERNAL $E43A] und bei LIST [BASIC $A6EA] genutzt). Aber sonst? Worauf müsste man denn sonst überhaupt Rücksicht nehmen (Einsprungadressen)?


    Einfach mal BIF fragen... :bgdev

  • Einfach mal BIF fragen... :bgdev

    Also quasi alle! :P Aber Spass beiseite, $ab1e find ich auch ganz praktisch. Damit kann man 0-terminierte Petscii-Strings ausgeben, in dem man deren Startadresse in A/Y an die Routine übergibt, und erspart sich damit die Schleifenprogrammierung. Geht aber nur bis zu Stringlängen von 256 Bytes incl. 0-Byte. Dann natürlich noch die ganzen Fliesskommaroutinen, falls man denn mal Fliesskomma braucht will man da bestimmt nicht "von Hand" mit rumrechnen.

  • ich habe kein Pkzip aktuell zur Hand, aber mal die KERNEL und BASIC Dateien (8192 Bytes jeweils groß) zusammengestaucht: die kommen auf zusammen 13,2 KB geRARt.


    Mit dem CharGen (4096 Bytes gr.) zusammen dazugepackt ist das Archiv nur 14 KB groß.


    Also unter Entropie-Gesichtspunkten ist noch ca. 30% "Luft" drin die man rauslassen könnte - rein theoretisch, und damit versuche ich die Ausgangsfrage dieses Threads näherungsweise zu beantworten. Um das 1:1 zu verwirklichen, müsste man z.b. die 151 möglichen opcodes mit einer arithmetischen Codierung zusammenkomprimieren, sodass jeder Opcode weniger als 1 Byte benötigt und diesen so entstandenen Zwischencode interpretieren. Aber da liegt es nahe, einfach von vornherein die Gesamtkonzeption zu ändern und wieder mit 6502 Code zu arbeiten, aber halt viel "freakiger".


    Der Gedanke erscheint verlockend, durch einen extravagant-knappen Programmierstil mit den Programmierkenntnissen von heute diese Firmware - bei Erhalt der Original-Funktionalität - so umzuschreiben, dass sie in insgesamt 16 KB passt (statt 20 KB wie im C64, einschließlich Zeichengenerator) und dabei nicht langsamer sondern allenfalls schneller arbeitet.


    Der Hintergedanke ist, dass man statt 3 nur 2 ROMs benötigt. Oder der Zeichengenerator mit im Kernel drin ist.


    Damit habe ich mal eine Zielmarke in den Raum gestellt ..

  • Da muss ich mal in meinen alten Zettelsammlungen graben, auf denen ich mir nützliche Routinen gesammelt habe.

    Mach mal :dafuer: .

    $ab1e find ich auch ganz praktisch.

    Stimmt, der ist doch schon mal gut!


    Mein Code in #19 ist übrigens doch Grütze :anonym . Abgesehen von einem falschen Branch (den ich leider nicht mehr editieren kann), flackert er natürlich, da $D011, -16, -18 vorübergehend genullt werden. Wenn man die nun noch in der Schleife abfragt, bleibt eine Ersparnis von ganzen 3 Byte :D . Also haben die Jungs das doch schon ganz richtig gemacht.

  • Puh, 20% kleiner halte ich für eine gewagte Prognose. Hie und da ist da bestimmt mal ein Byte zu viel im Commodore ROM, aber das ist schon eine ganze Menge. Das geht wohl nur mit deutlichen Funkionalitätseinschränkungen. Und das Testen ist bestimmt ein Albtraum, das haben sie bei Commodore schließlich auch nicht so spitzenmäßig hingekriegt. Hört sich nach enormen Aufwand an, aber wenn einer Spaß dran hat... :/

  • Wenn Du es schaffst dem 6510 die dekomprimierung von arithmetisch komprimierten Daten beizubringen, könnte
    Deine Rechnung aufgehen.

    Nichts leichter als das: es wird ein bytegrenzen-überschreitender variabler Zwischencode geschaffen der von einem in 6502 geschriebenen speziellen Interpreter abgearbeitet wird. Und die Basic/kernel-Firmware wird dann wiederum in dieser Zwischensprache realisiert - in der Summe würde ein BASIC-Programm auf so einem Rechner dann doppelt interpretiert, auf 2 Ebenen, doppelstöckig.


    Ist natürlich grottenlangsam, aber das ist nix neues...


    Siehe auch Sweet 16 (bereits an anderer Stelle erwähnt) der ebenfalls kompakte Programme ermöglicht.

  • Na dann leg mal los. :bgdev


    Die mit Rar erzielten 30% wirst Du aber
    nur mit arithmetischer Kodierung
    des Kernels ganz sicher nicht erreichen.


    Kleine Unwichtige Anekdote:


    Meine ersten Gehversuche mit C/C++ waren mit dem 1987 im CACM veröffentlichten
    Code für arithmetische Codierung.

  • Danke für den Hinweis!
    :platsch:



    Auch die Operanden nach den Opcodes folgen einer gewissen Verteilung, bzw. sind nicht gleichverteilt. Die könnte man in einer Liste aufzählen (es werden wohl nicht mehr als 1024 sein) und dahinein einen z.b. variablen, nur max. 10bittigen Kurzindex verwenden.


    Der natürlich weiterhin verbesserbare Code-ReUse bleibt einem als Strategie unbenommen.


    Claus: Beiträge haben sich überschnitten. Zu deinem Einwand: ich würde selbstverständlich nicht Funktionalität einschränken! das wäre doch völlig "unsportlich"!


    Nur zur Einschätzung was möglich ist:


    Die Zeitschrift 64'er hat damals zum Listingabtippen für den C64 zwei Programme propagiert, Checksummer und MSE (MaschinenSprachEditor).


    Als der C16 /116 /plus4 rauskam, veranstaltete sie einen Wettbewerb um den MSE auf die neue Plattform anzupassen und so dem Mangel an Software insgesamt abzuhelfen.


    Der Original MSE für C64 war 8KB groß.


    Mein Wettbewerbsbeitrag war auf Diskette von vornherein nur 5 KB groß und warf obendrein den größten Teil nach der Initialisierung des Programms wieder über Bord, so dass der im Speicher verbleibende Teil nur 600 Bytes groß war. Das habe ich mit Absicht so angestrebt, damit auch bis zu 12 KB große Programme für den C16 ohne Speichererweiterung ohne Probleme verarbeitet werden konnten. Der konkurrierende Einsender hatte übrigens nur vom vorhandenen MSE alle Kernel-Einsprungpunkte ausgetauscht, sein Beitrag war sogar länger als das C64- Original - eine quälend langweilige Arbeit, vor der ich mich gedrückt habe ;)


    Bei der Implementierung habe ich nur das Vorbild-Programm im Betrieb beobachtet und den entscheidenden Teil, 100 Bytes Prüfsummenberechnung, übernommen. Ich habe natürlich den gesamten MSE disassembliert und dabei haben sich mir immer mehr die Zehennägel aufgestellt vor Umständlichkeit ;) Der Entschluß, nichts davon zu übernehmen bis auf den Kern-Algorithmus, reifte in mir in Rekordzeit ...


    Daher glaube ich, dass durch unbefangenen Beobachter-Blick und Neu-Implementierung erhebliche Einsparungen auch bei der C64-Firmware möglich sind. Ob dies gelingt, bevor die meisten hier im Forum in Rente gegangen sind, lasse ich mal dahingestellt....

  • Ob man aber da Rückschlüsse auf die Commodore-Implementierung ziehen kann...? Da waren zumindest bezahlte Programmierer vermutlich in Vollzeit beschäftigt, soooo schlecht kann der Code also eigentlich nicht sein (und sind die Teile, die ich bislang gesehen habe auch nicht). Aber die Tools waren damals natürlich noch sehr einfach, was das Programmieren wohl durchaus schwieriger gemacht hat.

  • Das wohl nicht; aber ich meine, dass mit vermehrter Anstrengung auch mehr möglich ist.
    Also ich glaube, dass die C64-Firmware erheblich "besser optimiert" ist als der MSE an dem ich mich versucht habe. Andererseits ist sie vielleicht nicht optimal konzipiert ...


    Auf Michael Steils Seite (auf Pagetable) finden sich Quelltexte des ursprünglichen MS-Basic, das wohl mal in 8080-Assembler (o.ä.) geschrieben war und dann auf 6502 adaptiert wurde. Da zum damaligen Zeitpunkt kein spezieller 6502-Assembler verfügbar war, wurde wohl ein PDP-11-Timesharingsystem zur Editierung und Compilierung eingesetzt; die 6502-Befehle wurden dabei als PDP-Makros mit oktalen Parametern <X umgesetzt. Dass da die Übersicht leiden kann, erscheint naheliegend. Auch findet sich dort ein wohl von einem Commodore-Bearbeiter eingefügter Kommentar im Quelltext von Monte Davidoff: "slow as a turtle" bei den Fließkommaroutinen ......


    Allein schon diese entsetzliche Verrenkung verdient nicht nur Hochachtung :applaus: in Anbetracht des Ergebnisses, sondern weckt auch die leise Vermutung, dass da eben eine Neuimplementierung "from scratch" direkt in echtem 6502-Assembler noch erhebliche Schätze an Einsparung heben lassen dürfte.

  • Ja, damals wurde echt unter harten Bedingungen gearbeitet. Ich erinnere mich, dass ich mangels Drucker meine ersten Schritte in 6502 Assembly auch mit Hilfe vieler kleiner Zettel mit Hand-abgeschriebenem Code aus meinem Disassembler (ein Basic-Programm ^^ ) getan habe. Na gut, da wird schon was möglich sein. Aber jedes 5. Byte einsparen ist echt ehrgeizig...