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

letzter Beitrag von Boulderdash64 am

Optimierungsgrad KERNAL /BASIC - fiktiver Talk

  • Da hatte ich es ja richtig gut, ich konnte wenigstens Vaters Schreibmaschine - rate mal welches Fabrikat? - verwenden und habe z.B. die Raute-Symbole aus = und / (mit Rückschritt plus Überdrucken) zusammengesetzt...


    Ob man sich von alternativen "Basic"s fruchtbare Anregung holen kann? z.b. Applebasic, oder Ataris eigene Version..
    Ehrgeizig: nun ja, wenn man wirklich ans Konzept / Gefüge rangehen kann und was verändern, besteht ja auch Hoffnung auf doppelten Lohn: kürzer /weniger ROM beansprucht, aber auch zeitlich schnellere Abarbeitung. EDIT: Zu schnell darf die Neuimplementierung von FOR ... NEXT nicht sein, aus Kompatibilitätsgründen muss FOR i =1 to 1000: NEXT (leere Zeitschleife) vermutlich weiterhin exakt 1 Sekunde dauern :rauch:


    Das schließe ich daraus, weil jemand mal sagte, dass das C128-Basic insgesamt langsamer geworden sei, weil mehr Code (und Fallunterscheidungen für Optionen/Funktionalität) abgearbeitet /durchgepflügt werden muss. (daher würde der 2 Mhz-Modus eigentlich mehr ein Trostpflaster darstellen um dies wieder teilweise zu kompensieren). im Umkehrschluß: kürzere Implementierung derselben Sache -> schneller.


    Siehst du / Seht ihr das auch so?


  • Ehrgeizig: nun ja, wenn man wirklich ans Konzept / Gefüge rangehen kann und was verändern, besteht ja auch Hoffnung auf doppelten Lohn: kürzer /weniger ROM beansprucht, aber auch zeitlich schnellere Abarbeitung. EDIT: Zu schnell darf die Neuimplementierung von FOR ... NEXT nicht sein, aus Kompatibilitätsgründen muss FOR i =1 to 1000: NEXT (leere Zeitschleife) vermutlich weiterhin exakt 1 Sekunde dauern :rauch:

    Naja, den "Lohn" schätze ich eher auf "proof-of-concept" ;-) Dein EDIT macht ja schon deutlich: wirklich optimiert wird das nur für Neuentwicklungen brauchbar sein. Wenn 100% Kompatibilät gefragt ist, weiß nicht, - kann man sich das auch schenken. Aber dem TE ging es ja glaube ich eh eher um die "theoretische" Frage. In der Praxis entscheidet man sich heute eh am besten für "lda #$35 sta $01" :-)

  • Naja, den "Lohn" schätze ich eher auf "proof-of-concept" ;-) Dein EDIT macht ja schon deutlich: wirklich optimiert wird das nur für Neuentwicklungen brauchbar sein. Wenn 100% Kompatibilät gefragt ist, weiß nicht, - kann man sich das auch schenken. Aber dem TE ging es ja glaube ich eh eher um die "theoretische" Frage. In der Praxis entscheidet man sich heute eh am besten für "lda #$35 sta $01" :-)

    Am wichtigsten ist beim C64 eh, dass der Befehl "POKE" sorgfältig und 100% kompatibel realisiert ist;
    beim ganzen Rest besteht Spielraum :)


    Hoogo: beides ist richtig! ja, es wurde gut optimiert; und JA, es sind erhebliche Verbesserungen bei Raum- & Zeitbedarf möglich. Daß das BASIC ursprünglich auf dem 8080 lief und dann "umgebogen" wurde auf 6502, muß meines Erachtens Folgen für die Gesamteffizienz gehabt haben - die man vielleicht bis heute nicht gleich sieht.


    Das BASIC wurde von Microsoft so angelegt, dass es möglichst "selbstgenügsam" ist und wenig Sprünge nach aussen (zum Kernal) aufweist. Das steht ausdrücklich so in der Analyse von Michael Steil


    Wenig Sprünge nach aussen bedeutet: nur zur Abfrage wie groß der Speicher ist, ob bestimmte Trigonometrische Funktionen eingebunden sind, die Konsoleneingabefunktion, die Konsolenausgabefunktion (hab ich was vergessen?).


    Diese geringe Verflechtung mit dem Kernel bedingt aber meines Erachtens einen ungenügend ausgeschöpften Code- ReUse und damit Verbesserungspotential, wenn man die strikte Trennung Basic-Kernal weiter aufweicht.


    Dann müssten noch grundlegende Entscheidungen überdacht werden:
    - welche Variablen in der Zeropage können gleichzeitig bzw. wechselweise von Kernal/Basic benutzt werden und was kann durch den eingesparten Platz zusätzlich in die Zeropage wandern?
    - ist das Speicherlayout zweckmäßig? Das MS-Basic geht ja davon aus, dass auch winzig-minimale RAM-Portionen , wenige KB wie im VC20 und Ultimax, für einen lauffähigen Betrieb ausreichen; wenn man sich über ein "vernünftiges" Minimum Gedanken macht, sind vielleicht effizientere Layouts denkbar.
    -"Polarität" von Flags in der Zeropage: wie schon hexworx gezeigt hat ist die initialisierung ein mögliches Feld für Optimierungen. Könnte man die Flags die Basic und Kernal "brauchen" nicht generalstabsmässig so planen, dass immer Null den brauchbaren Standardwert darstellt? sodass die Speicherprüfung am Anfang einfach alles auf Null setzt und basta? Anstatt LDA #wert STA zp - Geprömpel ..



    Zum Thema Kompatibilität: da schwebt mir vor, die Codebasis an vorhandenen Programmen maschinell abzugrasen und statistisch auszuwerten nach "Verwendungszwecken" und "Einsprungpunkten ins ROM". Am Ende steht dann so eine Aussage wie: "wir legen fest, dass maximal 75% der Codebasis mit dem neuen ROM laufen können soll".

  • Das Ergebnis war in etwa, dass der C116 /plus4 einen wesentlich, fast möchte ich sagen, drastisch höheren Anteil des Opcodes $20 (= JSR) in seiner Firmware hatte; ebenso der C128, im Vergleich zum C64. dabei wurde nicht stupide das Byte $20 gesucht, sondern der gesamte Text disassembliert und nur wenn $20 am Anfang eines Maschinenbefehls stand, mitgezählt.

    Das liegt m.E. vor allem daran, dass der einfache Speicherzugriff LDA/STA (ZP),Y wegen des Bankings über Subroutinen geführt werden (ROM ausblenden, Zugriff, ROM einblenden oder die entsprechende Bank auswählen etc.). Das durchsetzt den Code dann damit weitgehend mit JSRs ...

  • Generell ist mir aufgefallen, dass BASIC und KERNAL des C64 solide codiert sind (Kleinigkeiten kann man mit etwas Anstrengung immer finden). Konzeptuell müsste man gröberes Anstellen, um wesentliche Einsparungen zu erreichen (ev. nur m Gegenzug mit anderen Abstrichen). Man merkt dem Code deutlich eine gewisse Platznot an. Code-Reuse (beim Exit einer Routine etc.) ist durchgehend in Verwendung. Manchmal auch mit dem Effekt, dass auch Bugs bei gewissen Konstellationen von Variablen oder Parametern auftreten. Das hat man aber wohl eher in Kauf genommen (sofern es bereits damals aufgefallen war).
    Offensichtlich wird dies, wenn man sich den Code der neuen Basic-Befehle etwa von BASIC 7.0 oder BASIC 3.5 ansieht. Der "Reuse" wird fast verschwenderisch außer Acht gelassen. Der Sprung zu einer Fehlermeldungsausgabe wird mehrfach innerhalb einer Page explizit codiert, obwohl auch "billig" per Bxx erreichbar, umständliche Schleifenführungen etc. Für mich ein Indiz für eine rasche Entwicklung, wenig Codereview (oder es gab keine Koordination bei mehreren Codern). Insgesamt ist einfach mehr "zusammen gestoppelt" worden. Es ist alles viel weniger aus einem Guss und man sieht dem Code die über die Jahre aus vielen Quellen stammenden Teile und Entwicklungen so auch ziemlich an.

  • Mir ist ja diese Zeilenverknüpfungstabelle $D9-F0 immer noch ein Dorn im Auge. Man hätte das auch größtenteils im ROM lassen können.


    Code
    1. ldy $d6 ; Cursorzeile
    2. lda linetab,y
    3. ora $0288 ; Ergebnis = High-Byte Zeilenanfang
    4. ;
    5. linetab !by 0,0,0,0,0,0,0, 1,1,1,1,1,1, 2,2,2,2,2,2,2, 3,3,3,3,3,3


    Die 'linetab'-Tabelle könnte man zum Beispiel in den ungenutzten Bereich bei $BF53 packen:


    Code
    1. .:BF53 AA AA AA AA AA
    2. .:BF58 AA AA AA AA AA AA AA AA
    3. .:BF60 AA AA AA AA AA AA AA AA
    4. .:BF68 AA AA AA AA AA AA AA AA
    5. .:BF70 AA


    Die Kennung für die 80-Zeichen-Zeilen müsste weiterhin natürlich im (ZP-)RAM bleiben. Man könnte es aber in 4 Adressen 'quetschen' (ungetestet):

    (Beispiel für Zeile auf 80-Zeichen setzen). Mit kleiner Abwandlung (EOR/AND) auch für 'löschen' verwendbar. Man müsste natürlich etliche Stellen anpassen und manches würde sich vielleicht verlängern/verlangsamen aber einige Sachen sogar auch kürzer/schneller werden (z. B. $E544).


    So oft wird die 40/80-Zeichen-Sache, meine ich, auch gar nicht benötigt/abgefragt, dass man das groß an der Geschwindigkeit merken würde. Man hätte aber 22(!) freie ZP-Adressen! Wäre natürlich traumhaft :rolleyes: .

  • wie sagte jeek so schön

    oder es gab keine Koordination bei mehreren Codern

    :bgdev;)
    jeek: Mir scheint, du verwendest "Code-Reuse" in einem viel konkreteren Sinn als ich. Ich meinte damit nur, dass ein Abschnitt Code von möglichst vielen "Verwendern" (anderen Code-Teilen) als Unterprogramm (oder Stub, oder Rucksack, oder Fehlermeldungs-Aussprung) "geteilt" wird, also recht abstrakt. Das bedeutet, eine UNterroutine sollte möglichst vielseitig und "nützlich" sein... Falls ich das Fachwort "Code-Reuse" damit überdehnt haben sollte in seinem Bedeutungsgehalt, bitte ich um Entschuldigung..
    Selbstverständlich müssen Mikes Erkenntnisse bei einer Neuimplementierung berücksichtigt werden :)


    Auf jeden Fall sehr lehrreich sind die Ausführungen von hexworx zu den Zeilen-Pointern ( 80 Z. Flags), und jeeks Ausführungen zur Codequalität bei Basic > 3,5. hexworx, hoffentlich machst du nicht wieder einen Rückzieher und schreibst wieder "Grütze" hinter deinen Code ;)

  • ich vermute die Antwort in diesem Thread:


    Die Fehler des Basic V2


    Mike schrieb dort (oder in einem anderen Thread) über seine Entdeckung, dass Commodore / MS da ein branch verrutscht ist, was bei manchen Multiplikationen dazu führt, dass von 4 Mantissenbytes eines nicht mehr bearbeitet wird und das Ergebnis dadurch grob falsch ist.


    Danach gab es Diskussionen, weil doch allen ernstes Leute sich darüber beschwert hatten, dass Mike angeblich eine "Inkompatibilität" heraufbeschwört, wo er doch einen sauberen Rucksack programmiert hat,
    ...
    Allerdings hilft einem die Forensuche aktuell nicht weiter
    ;(

  • 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....

    In der Tat, das ist meiner Meinung nach auch jener Ansatz, der das meiste Potenzial hätte, eine wesentliche Platz- und Laufzeiteinsparung zu erreichen. Die paar Späne, die beim Herumfeilen des Bestehenden abfallen, lohnen den Aufwand vermutlich nicht.
    Problem ist aber dennoch die Kompatibilität mit den besagten Einsprungpunkten, die natürlich nicht alle Adressen betrifft, aber um eine 99,9% Kompatibilität zu erreichen, werden da so gefühlt vielleicht doch rund 50 Einsprungpunkte zu berücksichtigen sein. ;)

  • Wenn ich diese Tabelle der Bildschirmzeilen richtig verstehe, dann hat die Sache mit den Fortsetzungszeilen im Verhältnis zum Aufwand eh sehr selten Nutzen.
    Hin und wieder braucht es zur Cursorposition die zugehörige Speicherstelle. Im Rahmen von Print ginge das Prima mit Inc oder Addieren, und im Direktmodus kommt es echt nicht auf Speed an.


    Muss man das Ganze nicht sogar als Buggy ansehen? Ein normales Print übers Zeilenende markiert zwar die Folgezeile als Fortsetzungszeile, erweitert die vorige Zeile aber nicht um 40 Leerzeichen, sondern printet einfach über das Vorhandene drüber. Trickst man mit Insert herum, dann wird aber eine echte Leerzeile eingefügt. Das ist irgendwie nicht konsequent. Und Cursor nach unten interessiert sich auch nicht dafür, ob die Zeile nun 40 oder 80 Zeichen enthält.


    Wenn man das nochmal von Scratch machen würde, dürfte das Ergebnis ganz anders aussehen.

  • jeek: Mir scheint, du verwendest "Code-Reuse" in einem viel konkreteren Sinn als ich. Ich meinte damit nur, dass ein Abschnitt Code von möglichst vielen "Verwendern" (anderen Code-Teilen) als Unterprogramm (oder Stub, oder Rucksack, oder Fehlermeldungs-Aussprung) "geteilt" wird, also recht abstrakt. Das bedeutet, eine UNterroutine sollte möglichst vielseitig und "nützlich" sein... Falls ich das Fachwort "Code-Reuse" damit überdehnt haben sollte in seinem Bedeutungsgehalt, bitte ich um Entschuldigung..

    Aber bitte, das ist ja nichts, wofür man sich entschuldigen müsste. Stimmt, der "Reuse" kann sich auf mehreren Ebenen auswirken. So wie ich schrieb, in der primitiven Form als "Abkürzer". Was du meinst, wäre gemeinhin auch als "Modularität" und Code-Faktorisierung bezeichenbar, mit dem Ziel, dass zum einen der Code so angelegt ist, dass möglichst viele Teile herausgeschnitten bzw. herausfaktorisiert werden können, damit etwas, dass das gleiche macht, tatsächlich nur genau nur ein mal implementiert ist.

  • Danke für den Begriff "Code-Faktorisierung", der hat mir gefehlt :ilikeit:

    aber um eine 99,9% Kompatibilität zu erreichen, werden da so gefühlt vielleicht doch rund 50 Einsprungpunkte zu berücksichtigen sein.

    ... die "neue" Firmware würde also beim Befehl "SYS" einen Hash-Wert über das Adreß-Argument bilden müssen und falls es einer jener 50 Einsprungpunkte ist, eine 'Adapter-Schicht' bzw. eine "Konverter-Stufe" aufrufen (alt/neu) :D


    Fehlt nur noch eine Idee, wie mit Maschinenprogrammen von a) Disk oder gar b) Modul zu verfahren ist... Idee: a) auch die Floppy kriegt eine neue Firmware, die die Dateien on-the-fly auf JSR "alter Einsprungpunkt" abklappert und eine Translation vornimmt; b) Lösung steht aus ..

  • Generell ist mir aufgefallen, dass BASIC und KERNAL des C64 solide codiert sind (Kleinigkeiten kann man mit etwas Anstrengung immer finden).

    Steht diese Aussage nicht im Widerspruch zur üblichen Aussage, das das BASIC so schlecht ist?

  • Wenn ich diese Tabelle der Bildschirmzeilen richtig verstehe, dann hat die Sache mit den Fortsetzungszeilen im Verhältnis zum Aufwand eh sehr selten Nutzen.

    -> Sorry wg. vermeintlichem / tatsächlichem Doppelpost


    Möglicherweise können diese Routinen bei einer Neuimplementierung fest auf 40 Zeichen bzw. mit Fortsetzungszeilen 80 Zeichen "festverdrahtet" werden; ich habs nicht mehr genau im Kopf, aber der Editor ist ja durch einige Metamorphosen gegangen, PET- > VC 20 -> C64 ... da schleppt er auch vermutlich einiges an "Universalität" / Konfigurierbarkeit herum die unnötig ist .

  • Steht diese Aussage nicht im Widerspruch zur üblichen Aussage, das das BASIC so schlecht ist?

    Diese Aussage stammt - wenn sie so pauschal getroffen wird - vermutlich von Leuten die mit quantitativem Argumentieren weniger vertraut sind.
    Absolut gesehen mag das BASIC "schlecht" sein weil es viele Problemlösungen nur suboptimal oder umständlich ermöglicht.
    Relativ gesehen, in Relation zum Speicherplatz der verfügbar ist, sieht es deutlich positiver aus.


    Und dann eben die Code-Qualität "under the hood", unter der Haube. Zu oft wird das c64-Basic mit Implementationen verglichen, die deutlich mehr ROM-Platz verbrauchen "durften".


    Bei einer Neuimplementierung würde ich versuchen, zuerst die genaue "Spezifikation" aus dem Maschinencode zu destillieren; also den Sprachumfang, die Funktionalität; erst später diese dann neu umsetzen. Wenn man das nicht getrennt hält, läuft man permanent in Versuchung , den Code abzukupfern und die Einsparungen nur durch Rausschmeißen von vermeintlich überflüssiger Funktionalität zu "erzielen".


    Oh, du arme arme Datasette! Was haben Generationen von Hobby-Kernel-Programmierern dir an Schmach und Würdelosigkeit angetan! Du ewig vertriebener Wanderer zwischen den ROM-Welten, heimatlos und ruhelos, wusstest nie, wo du deine Routinen betten durftest oder eine feste Bleibe bekamst, immer verscheucht von den bösen Disk-Speedern und anderen "Optimierungen" ;)

  • Zitat von oobdoo

    Steht diese Aussage nicht im Widerspruch zur üblichen Aussage, das das BASIC so schlecht ist?

    Gemessen an den Möglichkeiten der C64-Hardware *ist* das BASIC schlecht. Und der KERNAL macht bei den Registern von VIC-II und SID kaum mehr, als diese auf Grundwerte zu initialisieren; aus Sicht des BASICs wäre es egal ob der VIC z.B. nur einen Textmodus hätte.


    Zitat von androSID

    Fehlerhafte Multiplikation? Gibt's da nähere infos dazu?

    Der von Stephan genannte Thread ist schon richtig, allerdings ist das eine wirklich sehr zerfaserte Diskussion.


    Jedenfalls ist das Ergebnis einer Multiplikation grob falsch, sobald einer der Faktoren zwei aufeinanderfolgende Nullbytes in der Mantisse aufweist, ein Zwischenergebnis wird dann (in einer wiederbenutzten Routine übrigens!) fälschlicherweise um ein Bit zuviel geschoben. Zu sehen z.B. hier:

    Code
    1. A=16777217:PRINT A*10,10*A

    Sowohl die Werte 16777217, 10, als auch das richtige Ergebnis 167772170 lassen sich ohne Rundungsfehler im Fließkommaformat des C64 darstellen, aber eine der beiden Multiplikationen liefert ein offensichtlich falsches Ergebnis.


    Mein Vorschlag eines Patches ist dann noch durch zwei Iterationen gegangen, der aktuelle Source steht in Post #308 des o.g. Threads - hier erzeugt das Patch-Programm leider fälschlicherweise ein 12K großes *.bin-File -, und die korrigierte Version für ein 8K großes *.bin-File steht in Post #444.

  • Was am Basic so furchtbar schlecht ist, ist das, was beim Benutzer ankommt (Zeilennummern, zwei-Zeichen-Variablen, keine Integerarithmetik, fehlende Kontrollstrukturen, ...) - unter der Haube ist dieser Rotz aber relativ effizient umgesetzt. Das widerspricht sich also nicht.
    Das eine große Negativbeispiel ist die Garbage Collection, aber selbst die hat eine Entschuldigung: bei der Speichergröße eines VIC-20 gibt es an dem Algorithmus nicht viel auszusetzen, erst die unveränderte Übernahme auf den riesigen Speicher des C64 war unverzeihlich.


    Zur Ursprungsfrage: Mir fällt auf Anhieb kein Teil von Kernal+Basic ein, der irgendwie besonders ineffizient programmiert wäre - mit den bereits erwähnten Datassettenroutinen kenne ich mich allerdings nicht aus. Die meisten offensichtlichen Verbesserungsmöglichkeiten wie z.B. schnellere serielle Busroutinen etc. gehen mit einer Änderung des Protokolls einher; einfach nur intern den Code aufpolieren dürfte insgesamt kaum etwas bringen.
    (Ich freue mich über Gegenbeweise, aber da muss schon mehr kommen als nur Entropieerhöhung durch Packprogramme. :))


    Nein, wenn es an Kernal und Basic etwas auszusetzen gibt, dann schon eher an den diversen Interfaces und Protokollen, nicht an der Codequalität.
    -Warum wird "End of Information" vor statt nach dem letzten Byte übertragen? Das hat zur Folge, dass es keine leeren Dateien gibt, sondern immer mindestens ein Byte übertragen werden muss.
    -Warum wird für "Cursorposition lesen" und "Cursorposition setzen" ein- und dieselbe Kernalfunktion verwendet? Das aufrufende Programm setzt oder löscht das Carrybit, ruft dann die Routine auf, und die sieht sich erstmal das Carrybit an. Zwei separate Einsprünge wären in jedem Fall besser. Später beim 128er wurde für "linke obere Ecke des Textfensters definieren" und "rechte untere Ecke des Textfensters definieren" die gleiche Carry-Technik verwendet, und auch dort habe ich nie kapiert, was das eigentlich soll.
    -Warum verändern CHKIN und CHKOUT nicht einfach die Vektoren für CHRIN und CHROUT? Immer wenn man per $ffd2 ein Zeichen ausgibt, wird über einen indirekten JMP eine Routine angesprungen, die erst einmal langwierig prüft, auf welches Gerät eigentlich derzeit die Ausgabe erfolgt. Man hätte auch einfach vorher, beim Setzen des Geräts per CHKOUT, den Zeiger verbiegen können, so dass nicht mehr geprüft werden muss...


    Zum Vergleich:
    Beim DOS-ROM sieht es anders aus. Der Programmierer Dave "The Butcher" Siracusa ist entweder ein Genie oder ein komplett Wahnsinniger: Fast keine der Routinen lässt sich einfach per JSR anspringen, da die meisten nicht mit RTS, sondern mit einem JMP zur Hauptschleife enden. Das mag damit zusammenhängen, dass es ursprünglich mal ein Multiprocessing-System mit zwei CPUs war, aber spätestens bei den Änderungen von der 1541 zur 1571 hätte man deutlich "sauberer" programmieren können. Jeder Blick ins ROM-Listing hat da sofort dicken Hals zur Folge... ^^